| 家族 | 成员 | 要点 |
|---|---|---|
| 算术 | + − * / % | 整数除法向零截断;% 只用于整型 |
| 移位 | << >> | 左移右补 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 位 */
C 没有真正的布尔类型(C99 前教科书时代的写法):零为假、一切非零为真。比较和逻辑操作符产生的是 int 的 0 或 1。写条件时直接把「真值判断」交给语言即可:
if (count != 0) /* 可以,但啰嗦 */ if (count) /* 惯用:非零即真 */ if (ptr) /* 指针判空:等价于 ptr != NULL */ if (!ok) /* 取反 */
左值(lvalue)是「能标识一个存储位置」的表达式,可以出现在赋值号左边;右值只是值。变量是左值,字面量是右值,a + b 是右值。解引用 *p 是左值——这正是指针能改写目标的原因(第 6 章展开)。赋值要求:左边必须是左值,所以 a + b = 3; 编译报错。
表达式里类型不一致时,编译器按固定规则「升格」后再算,全程不需要你写 cast——但也因此容易不知不觉丢精度:
char c = 200; /* char 通常是有符号:实际存成 -56 */
int a = -1;
unsigned u = 1;
if (a < u) /* 假!a 被转成 unsigned,变成 42 亿 */
printf("less\n"); /* 不会执行——有符号 vs 无符号经典陷阱 */优先级决定怎么加括号,但不决定先算谁后算谁。除了 &&、||、?:、逗号四个有序操作符外,C 不规定同表达式中各操作数的求值顺序:
c = c + 1; /* OK */ c = c++ + 1; /* 未定义!同一序列点两次修改 c */ i = i++ + ++i; /* 未定义! */ x = f() + g(); /* f 和 g 谁先跑?未指定——有依赖时拆开写 */
可靠的写法只有一条:一个表达式里别对同一变量又读又写。拆成多条语句,顺序自己说了算。