运算符与表达式:C语言的“计算引擎”

各位同学,今天我们来聊聊C语言里最“实在”的部分——运算符与表达式。说白了,程序就是数据的搬运和加工,而运算符就是那个加工工具。我刚开始学C语言时,觉得运算符不就是加减乘除嘛,有啥好学的?结果写出来的代码要么结果不对,要么莫名其妙地崩溃。嗯,后来我才明白,这里面的门道可不少。

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

算术运算符就是我们从小用到大的加减乘除,但在C语言里,它们有自己的一套规矩。

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

这里有个坑,我当年就踩过。整数除法会直接截断小数部分,而不是四舍五入。比如 5 / 2 的结果是2,不是2.5。你想想看,如果你在做平均分计算,这个细节没注意,结果就全错了。

⚠️ 注意:取模运算符 % 只能用于整数,不能用于浮点数。我曾经在项目里用浮点数取模,编译器报错我还找了半天原因。
int a = 10, b = 3;
int result1 = a / b;  // 结果是3
int result2 = a % b;  // 结果是1
float result3 = 10.0 / 3.0;  // 结果是3.333333

关系运算符:判断真假的基础

关系运算符用来比较两个值的大小或是否相等。结果只有两种:真(1)或假(0)。我个人习惯把关系运算符看作“问问题”——你问“a大于b吗?”,它回答“是”或“否”。

运算符 含义 示例
== 等于 a == b
!= 不等于 a != b
> 大于 a > b
< 小于 a < b
>= 大于等于 a >= b
<= 小于等于 a <= b
💡 小技巧:新手最容易犯的错误是把 == 写成 =。前者是比较,后者是赋值。我建议你写条件判断时,把常量写在左边,比如 5 == a,这样万一写成 5 = a,编译器会直接报错。

逻辑运算符:组合条件的利器

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

  • &&(逻辑与):两边都为真,结果才为真。有一个假,结果就是假。
  • ||(逻辑或):只要有一个为真,结果就为真。两边都假,结果才假。
  • !(逻辑非):取反。真变假,假变真。

这里有个重要的特性叫“短路求值”。什么意思呢?对于 &&,如果左边已经是假,右边就不会再计算了。对于 ||,如果左边已经是真,右边也不会计算。我在项目中遇到过一个问题:代码里写了 if (ptr != NULL && ptr->value == 10),如果 ptr 是 NULL,右边根本不会执行,也就不会发生空指针访问。这就是短路求值的好处。

int a = 5, b = 10;
if (a > 0 && b > 0) {
    // 两个条件都成立
}
if (a > 0 || b < 0) {
    // 至少一个条件成立
}
if (!(a > b)) {
    // a不大于b时成立
}

赋值运算符:不只是等号

赋值运算符 = 用来把右边的值赋给左边的变量。但C语言还提供了一组复合赋值运算符,比如 +=-=*=/=%= 等。

我个人特别喜欢用复合赋值运算符,写起来简洁,而且不容易出错。比如 a += 5 等价于 a = a + 5,但前者更直观,也少写了一个变量名。

int count = 10;
count += 5;   // count变成15
count -= 3;   // count变成12
count *= 2;   // count变成24
count /= 4;   // count变成6
count %= 3;   // count变成0

位运算符:嵌入式开发的必备技能

位运算符直接操作二进制位,在嵌入式开发中特别常用。我当年做单片机项目时,经常用位运算来设置寄存器的某一位。

运算符 含义 示例
& 按位与 a & b
| 按位或 a | b
^ 按位异或 a ^ b
~ 按位取反 ~a
<< 左移 a << 2
>> 右移 a >> 2
🔑 核心用法:
  • 按位与 &:用来清零某一位(与0)或保留某一位(与1)
  • 按位或 |:用来置位某一位(或1)
  • 异或 ^:用来翻转某一位(异或1)
  • 左移 <<:相当于乘以2的n次方
  • 右移 >>:相当于除以2的n次方
unsigned char reg = 0b10101010;
// 将第3位(从0开始)置1
reg |= (1 << 3);   // 结果:0b10101010 | 0b00001000 = 0b10101010
// 将第5位清零
reg &= ~(1 << 5);  // 结果:0b10101010 & 0b11011111 = 0b10001010
// 翻转第1位
reg ^= (1 << 1);   // 结果:0b10001010 ^ 0b00000010 = 0b10001000

运算符优先级:谁先谁后?

运算符优先级决定了表达式中哪个运算先执行。我见过太多因为优先级搞错而导致的bug了。比如 a + b * c,乘法先执行,加法后执行。但如果你想让加法先执行,就得加括号 (a + b) * c

我个人有个习惯:拿不准优先级的时候,就用括号。括号不仅让代码更清晰,也避免了潜在的bug。你想想看,几个月后你回头看自己的代码,括号能帮你省多少脑细胞。

优先级 运算符 结合性
1(最高) () [] -> . 从左到右
2 ! ~ ++ -- + -(一元) *(解引用) &(取地址) 从右到左
3 * / % 从左到右
4 + - 从左到右
5 << >> 从左到右
6 < <= > >= 从左到右
7 == != 从左到右
8 &(按位与) 从左到右
9 ^ 从左到右
10 | 从左到右
11 && 从左到右
12 || 从左到右
13 ?:(三目运算符) 从右到左
14 = += -= *= /= %= &= |= ^= <<= >>= 从右到左
15(最低) , 从左到右
⚠️ 避坑指南:我曾经在项目里写过 if (a & b == 0),本意是判断 a 和 b 按位与的结果是否为0。但 == 的优先级高于 &,所以实际执行的是 if (a & (b == 0))。正确的写法是 if ((a & b) == 0)。记住:拿不准就加括号!

知识体系总览

下面这张图帮你理清本章的知识结构,方便你对照学习。

运算符与表达式 算术运算符 + - * / % 关系运算符 == != > < >= <= 逻辑运算符 && || ! 赋值运算符 = += -= *= /= 位运算符 & | ^ ~ << >> 运算符优先级 15个优先级等级 核心原则:拿不准就加括号 短路求值 | 类型转换 | 结合性

好了,运算符这部分就讲到这里。记住:多写、多练、多踩坑,才能真正掌握。下次写代码时,遇到复杂的表达式,先停下来想想优先级,或者干脆加个括号。相信我,这个习惯能帮你省下不少调试时间。


公众号:蓝海资料掘金营,微信deep3321