知识点

C运算符优先级

定义

C的表分15级,同一行的运算符优先级相同。判定规则与Python一致:优先级高的先结合,同一优先级按结合方向分组。但两级之间的分界不同,C把按位运算放在比较之下,这是最容易误判的一处。

优先级运算符结合性描述与示例
1() [] -> . ++ --(后缀)左到右函数调用、数组下标、成员访问、后缀自增自减
2! ~ ++ --(前缀) + -(一元) * & (type) sizeof右到左单目运算符。*p++ 等价于 *(p++):先取值,再让指针自增
3* / %左到右乘、除、取模。5 / 2得2,整数除法截断
4+ -左到右加、减
5<< >>左到右移位
6< <= > >=左到右关系运算
7== !=左到右等于、不等于。if (a = b) 是赋值表达式,不是比较
8&左到右按位与
9^左到右按位异或
10|左到右按位或
11&&左到右逻辑与,短路。优先级低于按位与 &
12||左到右逻辑或,短路
13?:右到左三目运算符
14= += -= *= /= %= <<= >>= &= ^= |=右到左赋值
15,左到右逗号运算符,值取最后一个表达式

性质

结合方向

C中只有单目运算符、三目运算符和赋值运算符右结合,其余左结合:

  • a = b = c等价于a = (b = c),赋值从右往左串起来;
  • a ? b : c ? d : e等价于a ? b : (c ? d : e);
  • i = 2, i + 3先执行i = 2,整个表达式的值是i + 3。

经典陷阱

*p++ 与 (*p)++ 的效果

deref-increment.c
#include <stdio.h>
int main(void) {
int a[3] = {1, 2, 3};
int *p = a;
int x = *p++; /* x = 1,p移到a[1] */
int y = (*p)++; /* y = 2,a[1] 变成3,p不动 */
printf("%d %d %d %d\n", x, y, a[0], a[1]); /* 1 2 1 3 */
return 0;
}

第一行移动的是指针,第二行修改的是数据。读这类表达式时,先按优先级补出括号,再判断动的是地址还是值。

a & b == c的分组

bitwise-compare.c
#include <stdio.h>
int main(void) {
int a = 6, b = 3, c = 2;
printf("%d\n", a & b == c); /* 6 & (3 == 2) = 6 & 0 = 0 */
printf("%d\n", (a & b) == c); /* (6 & 3) == 2,即2 == 2,得1 */
return 0;
}

两个表达式只差括号,结果一个是0、一个是1。位运算与比较同时出现时,按本意加括号。

探索

自增改变指针还是指向的值?

比较 *p++ 与 (*p)++ 对指针和数组元素的影响。

正在准备演示…

题集

练习

  1. int a = 6, b = 3, c = 2; 时,表达式a & b == c的值是 。

    参考解答

    比较先算,3 == 2为0,再算6 & 0,结果为0。本意若是 (a & b) == c,要自己加括号。

  2. int a[3] = {1, 2, 3}; int *p = a; int x = *p++; 之后,x的值是 ,*p的值是 。

    参考解答

    *p++ 等价于 *(p++):先取a[0] 的值给x,再把p移到a[1],所以 *p是2。

  3. int a = 5; if (a = 0) { /* 分支 */ } 中条件的结果是:

    选择答案
    参考解答

    赋值表达式把0赋给a,整个表达式的值就是0,因此条件为假。C允许赋值出现在条件中,编译器通常只警告;比较应写a == 0。

  4. int i = 0; 之后,表达式i = 2, i + 3的值是 。

    参考解答

    逗号运算符的优先级最低,先执行i = 2,再求i + 3,整个表达式的值取最后一个,即5。

  5. 表达式0 ? 1 : 2 ? 3 : 4的值是 。

    参考解答

    三目运算符右结合,等价于0 ? 1 : (2 ? 3 : 4)。条件0为假,取后一支;内层条件2为真,取3。

  6. 在32位int的平台上,-7 / 2的值是 。

    参考解答

    C的整数除法向零截断,商的整数部分是 −3-3。同样的式子写成Python的 -7 // 2,结果是 −4-4,因为Python向下取整。