跳转至

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。程序能编译但判断总是不对时,先看看是不是把 == 写成了 =

if (x = 0) { ... }   /* 给 x 赋 0,条件恒假 */
if (x == 0) { ... }  /* 判断 x 是否为 0 */

逻辑非、与、或分别为 !&&||。C 中 0 为假,非 0 为真。&&|| 采用短路求值:

if (p != NULL && *p > 0) { ... }  /* p 为空时不会计算 *p */

另一个常见坑是 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> 中常用函数包括 sqrtpowfabsexplogsincos。参数和返回值通常为 double

#include <math.h>
double distance = sqrt(x * x + y * y);

在部分 Unix 编译环境中,链接时还要加 -lm

4.4 宏定义

#define PI 3.141592653589793
#define SQUARE(x) ((x) * (x))
#define TO_STRING(x) #x

宏就是预处理阶段的文本替换,没有函数那样的类型检查。带参数宏的坑主要有两个:一是括号,二是参数可能被展开多次。上面的 SQUARE(i++) 会把 i++ 展开两遍,绝不是普通函数调用的效果。

宏替换体里的 # 把实参变成字符串,## 把两个记号拼在一起;多行宏用反斜杠续行。普通常量用 const 往往更清楚,简单计算也尽量写函数,宏留给确实需要文本替换的地方。

评论