第5章:运算符与表达式——C语言的“动作指令”

运算符,说白了就是C语言里的“动作指令”。你写下一行代码,CPU就按照这些符号去执行加减乘除、比较大小、判断真假、甚至直接操作二进制位。我刚开始学C的时候,总觉得运算符就是些符号,没什么好学的。直到有一次调试一个串口通信程序,死活收不到正确数据,最后发现是位运算符用错了——嗯,从那以后我再也不敢小看这些“小符号”了。

5.1 算术运算符:最基础的数学工具

算术运算符就是咱们从小用到大的加减乘除,但在C语言里多了个取模(%)。我个人习惯把取模叫做“求余数”,它特别适合判断奇偶、控制循环范围。

int a = 10, b = 3;
int sum = a + b;    // 13
int diff = a - b;   // 7
int prod = a * b;   // 30
int quot = a / b;   // 3(整数除法,舍去小数)
int rem = a % b;    // 1(余数)

⚠️ 注意:整数除法会直接截断小数部分,不是四舍五入。比如 10/3 结果是3,不是3.333。如果你想得到小数,至少一个操作数要写成浮点数:10.0/3。

自增自减运算符(++ 和 --)也是算术家族的一员。它们分前缀和后缀,这个区别我踩过坑。

int x = 5;
int y = x++;  // y=5, x=6(先赋值,后自增)
int z = ++x;  // z=7, x=7(先自增,后赋值)

💡 我的建议:在复杂表达式中尽量别用 ++ 和 --,容易把自己绕晕。单独一行写自增自减,清晰又安全。

5.2 关系运算符:比较大小,判断真假

关系运算符就是 >、<、>=、<=、==、!=。它们的结果只有两个:真(1)或假(0)。

int score = 85;
int passed = score >= 60;  // passed = 1(真)
int failed = score == 59;  // failed = 0(假)

⚠️ 我曾经犯过的错:把 == 写成 =。if (x = 5) 在C语言里是合法的——它把5赋值给x,然后判断x是否为真(非0即为真)。这个bug我找了一下午。记住:判断相等用两个等号,赋值用一个等号。

5.3 逻辑运算符:组合条件判断

逻辑运算符有三个:&&(与)、||(或)、!(非)。它们用来组合多个条件。

int age = 25;
int hasID = 1;
int canEnter = (age >= 18) && (hasID == 1);  // 两个条件都满足才为真

int isWeekend = 0;
int isHoliday = 1;
int canSleep = isWeekend || isHoliday;  // 只要一个为真就为真

int isClosed = !canEnter;  // 取反

🔑 短路求值:这是逻辑运算符的一个重要特性。对于 &&,如果左边为假,右边就不执行了。对于 ||,如果左边为真,右边也不执行了。我在项目中利用这个特性写过这样的代码:

if (ptr != NULL && ptr->value > 10) {
    // 只有ptr不为空时,才会访问ptr->value
}

5.4 位运算符:直接操作二进制位

位运算符是C语言的“硬核”部分,直接操作变量的二进制位。嵌入式开发中经常用到,比如设置寄存器、打包数据。

运算符名称示例说明
&按位与0b1010 & 0b1100 = 0b1000两个位都为1时结果为1
|按位或0b1010 | 0b1100 = 0b1110只要一个位为1结果就为1
^按位异或0b1010 ^ 0b1100 = 0b0110两个位不同时结果为1
~按位取反~0b1010 = 0b01010变1,1变0
<<左移0b0001 << 2 = 0b0100左移n位相当于乘以2ⁿ
>>右移0b0100 >> 2 = 0b0001右移n位相当于除以2ⁿ
// 嵌入式开发中常用的位操作
unsigned char reg = 0x00;

// 设置第3位为1(从0开始计数)
reg |= (1 << 3);      // reg = 0x08

// 清除第3位
reg &= ~(1 << 3);     // reg = 0x00

// 翻转第3位
reg ^= (1 << 3);      // reg = 0x08

⚠️ 注意:有符号整数的右移是算术右移(高位补符号位),无符号整数的右移是逻辑右移(高位补0)。我建议位操作一律用无符号类型,避免意外。

5.5 赋值运算符:给变量“喂”值

赋值运算符 = 是最常用的。但C语言还提供了复合赋值运算符,比如 +=、-=、*= 等,它们能让代码更简洁。

int x = 10;
x += 5;   // 等价于 x = x + 5,x变成15
x -= 3;   // 等价于 x = x - 3,x变成12
x *= 2;   // 等价于 x = x * 2,x变成24
x /= 4;   // 等价于 x = x / 4,x变成6
x %= 4;   // 等价于 x = x % 4,x变成2

💡 我个人习惯:能用复合赋值就用复合赋值。不仅代码更短,而且变量只写一次,编译器优化起来也方便。

5.6 逗号运算符:把多个表达式串起来

逗号运算符可能是最容易被忽略的。它把多个表达式串在一起,整个表达式的值等于最后一个表达式的值。

int a = (1, 2, 3, 4, 5);  // a = 5,前面的1,2,3,4都被执行但被丢弃

// for循环中常用
for (int i = 0, j = 10; i < j; i++, j--) {
    printf("i=%d, j=%d\n", i, j);
}

🔑 注意:逗号运算符的优先级是所有运算符中最低的。所以如果你在别的地方用逗号,记得加括号。

知识体系总览

下面这张图把本章的运算符家族梳理了一遍,方便你对照记忆:

C语言运算符 算术运算符 + - * / % ++ -- 关系运算符 > < >= <= == != 逻辑运算符 && || ! 位运算符 & | ^ ~ << >> 赋值运算符 = += -= *= /= %= 逗号运算符 , (优先级最低) 运算符优先级:! > 算术 > 关系 > && > || > 赋值 > 逗号 记不住优先级?加括号!加括号!加括号!

运算符优先级速查

优先级决定了表达式里谁先算。记不住没关系,我有个笨办法:拿不准就加括号。代码是给人看的,不是给编译器看的。

优先级运算符结合性
1(最高)() [] -> .左到右
2! ~ ++ -- + - * & (类型) sizeof右到左
3* / %左到右
4+ -左到右
5<< >>左到右
6< <= > >=左到右
7== !=左到右
8&左到右
9^左到右
10|左到右
11&&左到右
12||左到右
13?:右到左
14= += -= *= /= %= &= ^= |= <<= >>=右到左
15(最低),左到右

💡 我的经验:写代码时别太依赖优先级表。多用括号把意图表达清楚,三个月后你自己回来看代码,也能一眼看懂。

好了,运算符这块就聊到这儿。记住:算术运算符管数值计算,关系运算符管比较,逻辑运算符管条件组合,位运算符管二进制操作,赋值运算符管变量更新,逗号运算符管表达式串联。每个都有它的用武之地,用对了能让代码既高效又优雅。


专注资料整理