📖 第 5 章:操作符和表达式(Operators and Expressions)

🔤 Kenneth Reek《Pointers on C》(C和指针)· Chapter 5
🗨️ 本章金句:「优先级表不用背全,但要会查;求值顺序的坑必须刻在脑子里。」
这一章在干嘛? 把 C 的操作符一网打尽,再讲清两件容易被忽略的事:表达式求值时的隐式类型转换,以及优先级 ≠ 求值顺序——后者是未定义行为重灾区。
5.1 操作符全景5.2 布尔值、左值与右值5.3 隐式类型转换:整型提升与算术转换5.4 优先级 ≠ 求值顺序:未定义行为重灾区

5.1 操作符全景

家族成员要点
算术+ − * / %整数除法向零截断;% 只用于整型
移位<< >>左移右补 0;右移有符号数左边补符号位(算术移位);移位数 ≥ 位宽是未定义
位操作& ^ | ~清零用 &、置位用 |、翻转用 ^;常用于寄存器操作
赋值= += −= <<= ...复合赋值少打字且语义精确;赋值表达式本身有值(= 右结合)
单目! ~ ++ −− + − * & sizeof++ 前置先加后用、后置先用后加;sizeof 是编译期求值
关系> >= < <= == !=结果是 0 或 1;== 和 = 手滑是最经典的 bug
逻辑&& ||短路求值:左边已定生死就不看右边;保证先算左边
条件?:唯一三目操作符;「表达式版的 if-else」
逗号,从左到右依次求值,取最右边的值;for 步进里常见
下标/调用/成员[] () . ->优先级最高的一档;a[i] 就是 *(a+i)
/* 位操作的寄存器三板斧 */
reg &= ~(1u << n);   /* 清第 n 位 */
reg |=  (1u << n);   /* 置第 n 位 */
reg ^=  (1u << n);   /* 翻转第 n 位 */

5.2 布尔值、左值与右值

C 没有真正的布尔类型(C99 前教科书时代的写法):零为假、一切非零为真。比较和逻辑操作符产生的是 int 的 0 或 1。写条件时直接把「真值判断」交给语言即可:

if (count != 0)   /* 可以,但啰嗦 */
if (count)        /* 惯用:非零即真 */
if (ptr)          /* 指针判空:等价于 ptr != NULL */
if (!ok)          /* 取反 */

左值(lvalue)是「能标识一个存储位置」的表达式,可以出现在赋值号左边;右值只是值。变量是左值,字面量是右值,a + b 是右值。解引用 *p 是左值——这正是指针能改写目标的原因(第 6 章展开)。赋值要求:左边必须是左值,所以 a + b = 3; 编译报错。

5.3 隐式类型转换:整型提升与算术转换

表达式里类型不一致时,编译器按固定规则「升格」后再算,全程不需要你写 cast——但也因此容易不知不觉丢精度:

char c = 200;          /* char 通常是有符号:实际存成 -56 */
int  a = -1;
unsigned u = 1;
if (a < u)             /* 假!a 被转成 unsigned,变成 42 亿 */
    printf("less\n");  /* 不会执行——有符号 vs 无符号经典陷阱 */
记住这条: 有符号与无符号比较时,有符号一方被转成无符号。-1 会变成巨大的正数。同时使用两族类型前先统一符号性。

5.4 优先级 ≠ 求值顺序:未定义行为重灾区

优先级决定怎么加括号,但不决定先算谁后算谁。除了 &&、||、?:、逗号四个有序操作符外,C 不规定同表达式中各操作数的求值顺序:

c = c + 1;            /* OK */
c = c++ + 1;          /* 未定义!同一序列点两次修改 c */
i = i++ + ++i;        /* 未定义! */
x = f() + g();        /* f 和 g 谁先跑?未指定——有依赖时拆开写 */

可靠的写法只有一条:一个表达式里别对同一变量又读又写。拆成多条语句,顺序自己说了算。

优先级速记口诀: 单目 > 算术 > 移位 > 关系 >(位与 > 位异或 > 位或)> 逻辑 > 条件 > 赋值 > 逗号。拿不准就加括号——括号不花钱,bug 才花钱。
🧠 小测验
1. 短路求值带来什么保证?举个惯用法。
&& 和 || 保证先求左边、且必要时才求右边。惯用法:if (p != NULL && p->value > 0)——先判空再解引用;while (i < N && a[i] != target)——先判边界再取下标,顺序反了就越界。
2. 为什么 if (-1 < 1u) 为假?
有符号与无符号比较时,-1 被隐式转换成 unsigned int,变成约 42 亿,大于 1,条件为假。混用两族整数前先统一符号性或显式 cast。
3. i = i++ 这一类的表达式错在哪?
同一序列点内对 i 既读又写且无顺序约束,属于未定义行为——编译器可以做任何事。优先级只决定结合方式,不决定求值顺序;改写成两条语句即可。
← 上一篇🏠 顶层目录下一篇 →