C 语言运算符

1. 引言

运算符(Operator)是 C 语言的基石,它告诉编译器要对一个或多个操作数执行特定的数学运算、逻辑运算或数值操作。在 C 语言中,运算符的种类丰富、优先级复杂,是初学者最容易踩坑的地方,也是进阶程序员编写高效代码的利器。

本文将从运算符的分类到具体用法,结合真实可运行的代码示例,带大家系统梳理 C 语言中的各种运算符,让你不再为 ++&&-> 等符号发愁。

2. C 语言运算符分类

C 语言中的运算符可以粗略分为以下几大类:

  • 算术运算符:+ - * / %
  • 关系运算符:> < >= <= == !=
  • 逻辑运算符:&& || !
  • 位运算符:& | ^ ~ << >>
  • 赋值运算符:= += -= *= /= %= <<= >>= &= |= ^=
  • 条件运算符(三元):? :
  • 逗号运算符:,
  • sizeof 运算符
  • 指针与成员访问运算符:* & -> .
  • 其它:如强制类型转换 (type)、函数调用 ()、下标 []

3. 算术运算符

算术运算符用于基本的数学计算。

运算符含义示例
+加法 / 正号a + b
-减法 / 负号a - b
*乘法a * b
/除法a / b
%取模(求余)a % b

注意:

  1. 除号 / 在两个整数相除时执行整数除法,结果舍去小数部分。如 5 / 2 结果为 2
  2. 取模运算符 % 的两个操作数都必须是整数,结果是整除后的余数,如 5 % 2 结果为 1

示例:

#include <stdio.h>

int main() {
    int a = 10, b = 3;
    printf("a + b = %d\n", a + b);   // 13
    printf("a - b = %d\n", a - b);   // 7
    printf("a * b = %d\n", a * b);   // 30
    printf("a / b = %d\n", a / b);   // 3 (整数除法)
    printf("a %% b = %d\n", a % b);  // 1
    return 0;
}

4. 关系运算符

关系运算符用来比较两个值的大小关系,结果是一个布尔值(C89 中用 0 表示假,非 0 表示真;C99 开始提供 _Bool 类型和 stdbool.h 中的 true/false)。

运算符含义示例
>大于a > b
<小于a < b
>=大于等于a >= b
<=小于等于a <= b
==等于a == b
!=不等于a != b

注意不要错把 == 写成 =(赋值),这是 C 语言中最常见的 bug 之一。

#include <stdio.h>
#include <stdbool.h>

int main() {
    int x = 10, y = 20;
    bool result = (x > y);   // false
    printf("x > y ? %d\n", result);
    printf("x == y ? %d\n", x == y); // 0
    printf("x != y ? %d\n", x != y); // 1
    return 0;
}

5. 逻辑运算符

逻辑运算符用于对布尔值进行“与或非”运算,通常与关系运算符结合使用。

运算符含义示例
&&逻辑与a && b
``
!逻辑非!a

短路特性

  • 对于 a && b,如果 a 为假,则不再计算 b,直接返回假。
  • 对于 a || b,如果 a 为真,则不再计算 b,直接返回真。

这个特性经常被用来编写安全的条件判断,例如:

if (ptr != NULL && ptr->value > 0) {
    // 安全:只有 ptr 非空才会访问 ptr->value
}

6. 赋值运算符

赋值运算符用于将一个值赋给一个变量。

  • 基本赋值:=
  • 复合赋值:+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=

复合赋值运算符相当于先执行运算再赋值,可以简化代码并可能优化效率。例如:

int a = 10;
a += 5;   // 等价于 a = a + 5;
a *= 2;   // 等价于 a = a * 2;
a %= 3;   // 等价于 a = a % 3;

7. 自增自减运算符

++-- 是 C 语言的特色,分别将变量的值增加 1 或减少 1。它们又分为前缀和后缀两种形式。

  • 前缀形式:++a / --a —— 先执行自增/自减,再使用新值进行表达式计算。
  • 后缀形式:a++ / a-- —— 先使用原值进行表达式计算,再自增/自减。
int a = 5, b;
b = ++a;  // a 先变为 6,然后 b = 6
b = a--;  // 先将 a(6)赋给 b,然后 a 变为 5

由于自增自减容易造成代码难以理解,建议不要在同一表达式里多次对同一个变量使用,例如 b = a++ + ++a; 的行为是未定义的。

8. 位运算符

C 语言可以直接对整数类型的二进制位进行操作,这是 C 强大且贴近底层的一面。

运算符含义示例
&按位与a & b
``按位或
^按位异或a ^ b
~按位取反(一元)~a
<<左移a << n
>>右移a >> n

注意

  • 左移相当乘 2 的幂,右移相当除以 2 的幂(注意符号位)。
  • 移位运算只对整数类型有意义。
  • 位操作常用于掩码、状态标志、加密、性能优化等场景。

示例:

unsigned char flag = 0b00001111;
flag |= (1 << 4);   // 设置第 4 位为 1
flag &= ~(1 << 3);  // 清除第 3 位
if (flag & (1 << 2)) {
    // 检查第 2 位是否为 1
}

9. 条件运算符(三目运算符)

条件运算符是 C 语言中唯一的三目运算符,语法为:

条件 ? 表达式1 : 表达式2

若条件为真,返回表达式1的值;否则返回表达式2的值。它常被用来简洁地代替简单的 if-else

int max = (a > b) ? a : b;
printf("较大的数是 %d\n", max);

10. 逗号运算符

逗号运算符 , 用于连接两个表达式,按从左到右的顺序依次执行,并返回最后一个表达式的值。它通常用在 for 循环的初始化和递增部分。

for (int i = 0, j = 10; i < j; i++, j--) {
    printf("i=%d, j=%d\n", i, j);
}

11. sizeof 运算符

sizeof 用于获取某个类型或变量在内存中所占的字节数,其值由编译器在编译时确定(除了 C99 的可变长数组)。它既可以带括号使用,也可以不带。

int a;
printf("int: %zu\n", sizeof(int));
printf("a: %zu\n", sizeof a);
printf("double: %zu\n", sizeof(double));

sizeof 在动态分配内存、计算数组元素个数时非常实用:

int arr[10];
int n = sizeof(arr) / sizeof(arr[0]);  // 10

12. 指针与成员访问运算符

C 语言的指针操作是一大核心,相关的运算符包括:

  • *:间接引用(解引用)运算符,获取指针指向的值。
  • &:取地址运算符,获取变量的地址。
  • ->:通过结构体指针访问成员。
  • .:通过结构体变量访问成员。
struct Point {
    int x, y;
};

struct Point p1 = {10, 20};
struct Point *ptr = &p1;

printf("p1.x = %d\n", p1.x);      // 使用 . 访问
printf("ptr->y = %d\n", ptr->y);  // 等效于 (*ptr).y

13. 运算符优先级与结合性

当一个表达式中出现多个运算符时,编译器需要按一定的规则决定运算顺序,这就是优先级和结合性。

  • 优先级:高优先级的运算符先参与运算。例如乘除优先于加减。
  • 结合性:当同一优先级的运算符同时出现时,表达式的计算顺序由结合性决定。大多数运算符是左结合(从左向右),少数是右结合(如赋值、条件、一元运算符)。

下面是一张简化版优先级表(从高到低):

优先级运算符结合性
1() [] -> .
2! ~ ++ -- +(正) -(负) *(指针) &(取地址) sizeof (type)
3* / %
4+ -
5<< >>
6< <= > >=
7== !=
8&(按位与)
9^
10``
11&&
12`
13? :
14= += -=
15,

建议:当不能确定优先级时,使用括号明确表达意图,括号拥有最高优先级且可读性最好。

// 容易产生歧义
int result = a + b * c;   // 等价于 a + (b * c)
// 推荐写法
int result = a + (b * c);

14. 运算符使用建议与常见问题

14.1 使用建议

1. 善用括号,提高可读性

运算符优先级虽然由语言规范严格定义,但人脑记忆有限。与其写出让人挠头的复杂表达式,不如多用括号把意图表达清楚:

// 不推荐:依赖优先级,容易读错
if (flag & mask == 0) { ... }          // & 优先级低于 ==

// 推荐:括号显式分组
if ((flag & mask) == 0) { ... }

2. 避免在复杂表达式中混用自增自减

在同一表达式中对同一变量多次使用 ++ / -- 不仅难以阅读,行为还可能未定义:

// 危险:行为未定义
int x = (i++) + (++i);

// 推荐:分开写
int x = i;
i++;
x += i;
i++;

3. 利用短路特性简化条件判断

短路求值是 C 语言的明确保证,可以放心用来避免空指针解引用:

// 安全的空指针检查
if (p != NULL && p->next != NULL) {
    p = p->next;
}

4. 用 sizeof 而不是硬编码数值

写死类型大小会导致跨平台问题,始终用 sizeof 计算:

// 不推荐
int *ptr = malloc(4 * n);        // 假设 int 是 4 字节

// 推荐
int *ptr = malloc(sizeof(int) * n);

5. 位运算优先用无符号类型

有符号整数在移位时的行为因编译器而异(尤其是右移),处理位掩码和位标志时优先使用 unsigned 类型:

unsigned int flags = 0;
flags |= (1U << 31);   // 安全设置高位

14.2 常见问题

Q1: === 搞混了怎么办?

这是 C 语言的头号陷阱。一个简单有效的习惯是:拿常量与变量比较时,把常量放在前面:

// 推荐写法:如果误写成 =,编译器会报错
if (0 == ret) { ... }
if (NULL == ptr) { ... }

因为 0 = ret 是非法的赋值语句,编译器会立刻拒绝,帮你发现问题。

Q2: 整数除法丢失小数怎么办?

当需要小数结果时,务必把操作数转换为浮点数再除:

int a = 5, b = 2;

// 不推荐:结果 = 2
double result = a / b;

// 推荐:显式转换为浮点
double result = (double)a / b;
// 或更清晰的写法
double result = 5.0 / 2;

Q3: 取模运算符能用负数吗?

可以,但结果的正负号依赖于 C 标准的版本:

printf("%d\n", -5 % 3);   // C89: 结果依赖于实现;C99: 结果为 -2

建议只对非负整数使用 %,或先转成正数再取模。

Q4: 三元运算符能嵌套吗?

能,但可读性会急剧下降:

// 不推荐:阅读困难
int grade = (score >= 90) ? 1 : (score >= 60) ? 2 : 3;

// 推荐:用 if-else 或提前计算
int grade;
if (score >= 90)      grade = 1;
else if (score >= 60) grade = 2;
else                  grade = 3;

Q5: 为什么 sizeof(arr) 在函数参数里不好使?

数组作为函数参数传递时会退化为指针,此时 sizeof 返回的是指针大小而非数组总大小:

void func(int arr[]) {
    // ⚠️ 始终返回指针大小(通常 4 或 8),不是数组元素数
    size_t n = sizeof(arr) / sizeof(arr[0]);
}

// 正确做法:把元素数也传进去
void func(int arr[], size_t len) {
    for (size_t i = 0; i < len; i++) { ... }
}

Q6: 位运算优先级低于比较运算符?

是的,这是高频踩坑点:

// 错误:先比较再按位与
if (flags & MASK == MASK) { ... }   // 等价于 flags & (MASK == MASK)

// 正确:括号包裹位运算
if ((flags & MASK) == MASK) { ... }

Q7: 逗号表达式的值到底是多少?

逗号运算符返回最后一个表达式的值,常用于 for 循环:

int a = (1, 2, 3);         // a = 3
int b = (a++, a * 2);      // a 先加 1,然后 b = (新 a) * 2

这种用法容易造成迷惑,建议只在 for 循环头部使用逗号运算符。

15. 总结

本文覆盖了 C 语言中几乎所有的运算符类型,从算术、关系、逻辑到位运算、赋值、条件、逗号、sizeof 以及指针相关,最后梳理了优先级和结合性。

掌握运算符不仅是写出正确代码的基础,更是理解复杂表达式、编写高效底层逻辑的关键。在练习中多动手,结合编译器的警告和优化,会让你对 C 语言的运算符有越来越深的把握。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Chengdu.S

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值