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

各位同学,欢迎来到第一章。今天咱们聊聊C语言里最基础、也最核心的东西——运算符和表达式。

你可能觉得这玩意儿太简单了,不就是加减乘除吗?嗯,我当年刚学的时候也这么想。直到有一次在项目里,因为一个运算符优先级的问题,排查了整整两天bug……从那以后,我再也不敢小看这些“小符号”了。

说白了,运算符就是C语言里的“动词”,表达式就是“句子”。没有它们,程序就是一潭死水。咱们今天就把这潭水搅活。

运算符分类与优先级体系 运算符 优先级 算术运算符 + - * / % 关系运算符 > < == != >= <= 逻辑运算符 && || ! 位运算符 & | ^ ~ << >> 赋值运算符 = += -= *= /= 优先级:单目 > 算术 > 关系 > 逻辑 > 赋值 括号 () 可以打破一切优先级规则

一、算术运算符:最熟悉的陌生人

加减乘除,小学就会。但在C语言里,有几个坑你得小心。

先看个例子:

int a = 10, b = 3;
int c = a / b;   // c = 3,不是3.333
int d = a % b;   // d = 1,取余数

看到了吗?两个整数相除,结果还是整数——直接截断小数部分。我当年做嵌入式电机控制时,就因为这个特性,算出来的转速总是偏小。后来改成浮点数才解决。

关键点:

  • / 对整数做除法,结果截断小数
  • % 只能用于整数,不能用于浮点数
  • 自增 ++ 和自减 -- 有前缀和后缀之分

说到自增自减,这里有个经典问题:

int x = 5;
int y = x++;   // y = 5, x = 6
int z = ++x;   // z = 7, x = 7

前缀先加再用,后缀先用再加。嗯,就这么简单。但嵌套在复杂表达式里就容易乱。我的建议是:别把 ++ 和 -- 塞进复杂表达式里,单独一行写,清晰明了。

二、关系运算符:真假判断

关系运算符用来比较两个值,结果要么是1(真),要么是0(假)。

运算符含义示例结果
>大于5 > 31
<小于5 < 30
==等于5 == 30
!=不等于5 != 31
>=大于等于5 >= 31
<=小于等于5 <= 30

⚠️ 我曾经踩过的坑:== 写成 =。在 if 条件里,if (x = 5) 不会报错,它会先把5赋给x,然后判断x是否为真(非0即为真)。结果就是条件永远成立!

我的习惯:把常量写在左边,比如 if (5 == x),这样万一写成 if (5 = x),编译器会直接报错。

三、逻辑运算符:组合判断

逻辑运算符用来组合多个条件:

  • && 逻辑与:两边都为真才真
  • || 逻辑或:一边为真即为真
  • ! 逻辑非:取反

这里有个重要特性——短路求值。你想想看:

int a = 0, b = 10;
if (a != 0 && b / a > 5) {
    // 这段代码永远不会执行
}

为什么?因为 a != 0 已经是假了,&& 后面的表达式根本不会执行。这就避免了除以零的错误。反过来,|| 也是,左边为真,右边就不看了。

我在做传感器数据采集时,经常用这个特性:

if (ptr != NULL && ptr->value > 100) {
    // 先检查指针是否有效,再访问数据
}

这样写既安全又高效。

四、位运算符:嵌入式工程师的利器

位运算直接操作二进制位,在嵌入式开发里用得特别多。说白了,就是跟寄存器打交道。

运算符含义示例
&按位与0x0F & 0xF0 = 0x00
|按位或0x0F | 0xF0 = 0xFF
^按位异或0x0F ^ 0xF0 = 0xFF
~按位取反~0x0F = 0xF0(假设8位)
<<左移0x01 << 3 = 0x08
>>右移0x08 >> 3 = 0x01

举个例子,你想设置某个寄存器的第3位为1:

REG |= (1 << 3);   // 置位第3位
REG &= ~(1 << 3);  // 清零第3位

这种写法在单片机开发里随处可见。我调试I2C驱动时,就靠位运算来操作控制寄存器,一个位一个位地配置。

小技巧:^ 异或可以不用临时变量交换两个数:

a = a ^ b;
b = a ^ b;
a = a ^ b;

不过说实话,可读性差了点。我一般只在内存极度受限的场合才这么干。

五、赋值运算符:不只是等号

最基本的赋值就是 =。但C语言还提供了复合赋值运算符,把运算和赋值合二为一:

int x = 10;
x += 5;   // 等价于 x = x + 5
x *= 2;   // 等价于 x = x * 2
x &= 0x0F; // 等价于 x = x & 0x0F

我个人很喜欢这种写法,简洁,而且编译器通常能生成更高效的代码。但要注意,别写得太花哨,比如 x += y *= z 这种——嗯,虽然语法上合法,但读起来费劲。

六、运算符优先级:最容易翻车的地方

优先级决定了表达式里谁先算。我直接给你一张表,按优先级从高到低排列:

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

你可能会问:这么多优先级,怎么记得住?

我的回答是:别死记,多用括号。括号的优先级最高,而且能让代码意图一目了然。

举个例子:

// 这样写,鬼知道先算哪个
int result = a + b * c << 2 & 0xFF;

// 加个括号,清清楚楚
int result = ( (a + (b * c)) << 2 ) & 0xFF;

我在代码评审时,看到那种靠优先级炫技的写法,一般都会要求加括号。代码是写给人看的,不是写给编译器看的。

核心原则:

  • 不确定优先级?加括号!
  • 觉得别人可能看不懂?加括号!
  • 宁可多写几个括号,也别让读代码的人猜

好了,运算符这块儿就聊到这儿。内容不少,但都是基本功。你写代码的时候多留意这些细节,慢慢就会形成肌肉记忆。下次咱们接着聊控制流程——if、switch这些,到时候这些运算符就派上大用场了。


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