4. C 表达式与宏定义
4.1 赋值、算术、关系与逻辑运算
赋值表达式本身也有值,值就是最后赋进去的内容;而且赋值从右向左结合,所以 a = b = 0 会先把 0 给 b,再给 a。
+=、-=、*=、/=、%= 是复合赋值。a += b + 3 等价于 a = a + (b + 3),右边的括号不要忘。
整数的 / 舍去小数部分,C99 起结果向 0 截断;余数满足 (a / b) * b + a % b == a,余数与被除数同号。% 只能用于整数。
关系运算的结果只有 0 或 1。程序能编译但判断总是不对时,先看看是不是把 == 写成了 =:
逻辑非、与、或分别为 !、&&、||。C 中 0 为假,非 0 为真。&& 和 || 采用短路求值:
另一个常见坑是 0 < x < 10。C 会先算 0 < x,得到 0 或 1,再拿这个结果和 10 比,所以这个条件几乎总为真。区间要拆开写成 0 < x && x < 10。
4.2 其他运算符
- 前置
++i:先增加,再产生新值;后置i++:先产生旧值,再增加; - 条件运算:
条件 ? 表达式1 : 表达式2,只计算其中一个分支; - 逗号运算:从左到右计算,整个表达式的值为最后一项的值;
- 取地址与间接访问:
&x、*p; - 成员访问:
.与->; - 数组下标和函数调用:
[]、()。
优先级只负责“怎么加括号”,不负责“先算左边还是右边”。这一点和数学式不太一样。读程序时如果一眼看不清,先自己补括号。
4.3 标准数学函数
<math.h> 中常用函数包括 sqrt、pow、fabs、exp、log、sin、cos。参数和返回值通常为 double。
在部分 Unix 编译环境中,链接时还要加 -lm。
4.4 宏定义
宏就是预处理阶段的文本替换,没有函数那样的类型检查。带参数宏的坑主要有两个:一是括号,二是参数可能被展开多次。上面的 SQUARE(i++) 会把 i++ 展开两遍,绝不是普通函数调用的效果。
宏替换体里的 # 把实参变成字符串,## 把两个记号拼在一起;多行宏用反斜杠续行。普通常量用 const 往往更清楚,简单计算也尽量写函数,宏留给确实需要文本替换的地方。