运算符与表达式: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)。记住:拿不准就加括号!
知识体系总览
下面这张图帮你理清本章的知识结构,方便你对照学习。
好了,运算符这部分就讲到这里。记住:多写、多练、多踩坑,才能真正掌握。下次写代码时,遇到复杂的表达式,先停下来想想优先级,或者干脆加个括号。相信我,这个习惯能帮你省下不少调试时间。
公众号:蓝海资料掘金营,微信deep3321