运算符优先级
C 语言有 40 多个运算符,优先级和结合性决定了复杂表达式的求值顺序。理解优先级规则能帮助你正确书写表达式,也能读懂没有过多括号的代码。但最佳实践是:不确定时加括号——代码的可读性比节省几个字符更重要。
优先级总表(从高到低)
| 优先级 | 运算符 | 结合性 | 说明 |
|---|---|---|---|
| 1 | () [] -> . | 左到右 | 函数调用、数组下标、成员访问 |
| 2 | ++ --(后缀) | 左到右 | 后缀自增自减 |
| 3 | ++ --(前缀) + - ! ~ * & sizeof (类型) | 右到左 | 前缀自增、正负号、逻辑非、按位取反、解引用、取地址、sizeof、强制转换 |
| 4 | * / % | 左到右 | 乘除取模 |
| 5 | + - | 左到右 | 加减 |
| 6 | << >> | 左到右 | 移位 |
| 7 | < <= > >= | 左到右 | 关系 |
| 8 | == != | 左到右 | 判等 |
| 9 | & | 左到右 | 按位与 |
| 10 | ^ | 左到右 | 按位异或 |
| 11 | | | 左到右 | 按位或 |
| 12 | && | 左到右 | 逻辑与 |
| 13 | || | 左到右 | 逻辑或 |
| 14 | ?: | 右到左 | 条件运算符 |
| 15 | = += -= 等 | 右到左 | 赋值 |
| 16 | , | 左到右 | 逗号运算符 |
常见优先级陷阱
赋值 vs 判等:
if (x = 5) /* 赋值,不是比较 */
if (x == 5) /* 比较 */
算术 vs 移位:
int result = a + b << 2; /* 等价于 (a + b) << 2 */
/* 不是 a + (b << 2) */
按位 vs 关系:
if (flags & MASK == VALUE) /* 等价于 flags & (MASK == VALUE) */
/* 不是 (flags & MASK) == VALUE! */
/* 正确 */
if ((flags & MASK) == VALUE)
/* ... */
这是最常见的优先级错误之一。== 的优先级高于 &,所以 flags & MASK == VALUE 被解析为 flags & (MASK == VALUE)。
逻辑 vs 按位:
if (a & b && c) /* 等价于 (a & b) && c */
/* 不是 a & (b && c) */
条件运算符优先级:
int result = a > b ? a : b + 1; /* 等价于 (a > b) ? a : (b + 1) */
/* 不是 ((a > b) ? a : b) + 1 */
/* 如果需要后者 */
int result = (a > b ? a : b) + 1;
赋值 vs 逗号:
int a = 1, 2; /* 声明中的逗号,不是运算符 */
int b = (1, 2); /* 逗号运算符:b = 2 */
结合性
结合性决定同优先级运算符的求值顺序:
左结合(从左到右):
int result = 10 - 5 - 2; /* (10 - 5) - 2 = 3 */
int result2 = 100 / 10 / 2; /* (100 / 10) / 2 = 5 */
右结合(从右到左):
int a = b = c = 0; /* a = (b = (c = 0)) */
int result = 2 << 1 << 2; /* (2 << 1) << 2 = 16(左结合) */
int *arr[10]; /* arr 是数组,元素是指针 */
int (*ptr)[10]; /* ptr 是指针,指向数组 */
最佳实践:使用括号
当表达式涉及多个运算符时,加括号消除歧义:
/* 容易误解 */
if (flags & mask == value)
/* 清晰 */
if ((flags & mask) == value)
/* 容易误解 */
int result = a > b ? a : b + c * d;
/* 清晰 */
int result = (a > b) ? a : (b + c * d);
括号不会降低性能——编译器会生成完全相同的机器码。它们提高的是代码的可读性和可维护性。
复杂声明的解析
C 语言的声明语法也涉及优先级,可以用"顺时针/螺旋规则"(Clockwise/Spiral Rule)解析:
int *arr[10]; /* arr 是数组([] 优先级高于 *),元素是 int* */
int (*ptr)[10]; /* ptr 是指针(括号优先),指向 int[10] */
int *func(int); /* func 是函数,返回 int* */
int (*fp)(int); /* fp 是指针,指向函数(参数 int),返回 int */
int *(*fparr[10])(int); /* fparr 是数组,元素是指针,指向函数(参数 int),返回 int* */
解析步骤:
- 从标识符开始
- 优先处理括号内的内容
- 向右看,遇到
[](数组)或()(函数) - 向左看,遇到
*(指针) - 重复直到覆盖整个声明
typedef 可以简化复杂声明:
typedef int (*FuncPtr)(int); /* FuncPtr 是函数指针类型 */
FuncPtr callbacks[10]; /* callbacks 是函数指针数组 */
常见错误
*p++ 的歧义:
int arr[] = {10, 20, 30};
int *p = arr;
int x = *p++; /* 等价于 *(p++):先取 *p,然后 p 递增 */
printf("%d\n", x); /* 10 */
printf("%d\n", *p); /* 20 */
int y = (*p)++; /* 先取 *p,然后 (*p) 递增 */
printf("%d\n", y); /* 20 */
printf("%d\n", arr[1]); /* 21 */
后缀 ++ 优先级高于解引用 *,所以 *p++ 是 *(p++),不是 (*p)++。
&arr[0] vs &arr:
int arr[5];
int *p1 = &arr[0]; /* 指向 arr[0],类型 int* */
int (*p2)[5] = &arr; /* 指向整个数组,类型 int(*)[5] */
[] 优先级高于 &,所以 &arr[0] 是 &(arr[0])。
sizeof 的优先级:
int a = 10;
printf("%zu\n", sizeof a); /* 合法:sizeof 是运算符,不是函数 */
printf("%zu\n", sizeof(a)); /* 等价 */
printf("%zu\n", sizeof(a + 1)); /* a + 1 是 int,sizeof(int) */
printf("%zu\n", sizeof a + 1); /* sizeof(a) + 1,不是 sizeof(a + 1)! */
sizeof 优先级高于 +,所以 sizeof a + 1 是 (sizeof a) + 1。