第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 = 0b0101 | 0变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);
}
🔑 注意:逗号运算符的优先级是所有运算符中最低的。所以如果你在别的地方用逗号,记得加括号。
知识体系总览
下面这张图把本章的运算符家族梳理了一遍,方便你对照记忆:
运算符优先级速查
优先级决定了表达式里谁先算。记不住没关系,我有个笨办法:拿不准就加括号。代码是给人看的,不是给编译器看的。
| 优先级 | 运算符 | 结合性 |
|---|---|---|
| 1(最高) | () [] -> . | 左到右 |
| 2 | ! ~ ++ -- + - * & (类型) sizeof | 右到左 |
| 3 | * / % | 左到右 |
| 4 | + - | 左到右 |
| 5 | << >> | 左到右 |
| 6 | < <= > >= | 左到右 |
| 7 | == != | 左到右 |
| 8 | & | 左到右 |
| 9 | ^ | 左到右 |
| 10 | | | 左到右 |
| 11 | && | 左到右 |
| 12 | || | 左到右 |
| 13 | ?: | 右到左 |
| 14 | = += -= *= /= %= &= ^= |= <<= >>= | 右到左 |
| 15(最低) | , | 左到右 |
💡 我的经验:写代码时别太依赖优先级表。多用括号把意图表达清楚,三个月后你自己回来看代码,也能一眼看懂。
好了,运算符这块就聊到这儿。记住:算术运算符管数值计算,关系运算符管比较,逻辑运算符管条件组合,位运算符管二进制操作,赋值运算符管变量更新,逗号运算符管表达式串联。每个都有它的用武之地,用对了能让代码既高效又优雅。