C语言操作符优先级与结合性详解
在C语言中,操作符的优先级和结合性是理解表达式求值规则的核心。很多“诡异”的bug都源于对优先级和结合性的误解。本文用一张表、几条规则和大量示例,带你彻底搞懂它们。

目录
- 1. 什么是优先级与结合性
- 1.1 优先级(Precedence)
- 1.2 结合性(Associativity)
- 2. C语言操作符优先级总表
- 3. 结合性详解
- 3.1 左结合(从左到右)
- 3.2 右结合(从右到左)
- 3.3 注意:函数参数中的逗号不是运算符
- 4. 常见陷阱与易错点
- 4.1 *p++ 到底是 (*p)++ 还是 *(p++)?
- 4.2 a & b == c 是逻辑错误
- 4.3 a << 2 + 1 的结果
- 4.4 a = b || c && d 的计算
- 4.5 x & 1 == 1 的解析
- 4.6 赋值运算符的返回值
- 4.7 条件运算符的嵌套
- 4.8 逗号运算符的优先级最低
- 5. 记忆口诀
- 6. 总结
1. 什么是优先级与结合性
1.1 优先级(Precedence)
当一个表达式中出现多个不同的操作符时,优先级高的操作符先参与运算。
例如:
int a = 2 + 3 * 4;
- 的优先级高于 +,所以先计算 3 * 4,再计算 2 + 12,结果为 14。
1.2 结合性(Associativity)
当表达式中出现多个相同优先级的操作符时,结合性决定它们从左到右还是从右到左进行分组。
例如:
int a = 10 – 3 – 2;
- 是左结合,所以等价于 (10 – 3) – 2,结果为 5。
再如赋值操作符 = 是右结合:
int a, b, c;
a = b = c = 0;
等价于 a = (b = (c = 0)),从右向左依次赋值。
注意:优先级和结合性只决定表达式的语法结构(即如何加括号),并不决定子表达式的求值顺序。C语言标准没有规定大多数子表达式的求值顺序,这可能导致未定义行为(Undefined Behavior),例如 i++ + ++i。
2. C语言操作符优先级总表
优先级从高到低排列。同一行优先级相同,结合性见每行标注。
| 1 | () [] . -> ++(后缀) –(后缀) | 函数调用、下标、成员访问、后缀自增/自减 | 左结合 |
| 2 | ++(前缀) –(前缀) + – ! ~ (type) * & sizeof | 前缀自增/自减、正负号、逻辑非、按位取反、类型转换、解引用、取地址、大小 | 右结合 |
| 3 | * / % | 乘法、除法、取模 | 左结合 |
| 4 | + – | 加法、减法 | 左结合 |
| 5 | << >> | 左移、右移 | 左结合 |
| 6 | < <= > >= | 关系比较 | 左结合 |
| 7 | == != | 等于、不等于 | 左结合 |
| 8 | & | 按位与 | 左结合 |
| 9 | ^ | 按位异或 | 左结合 |
| 10 | || | 按位或 | 左结合 |
| 11 | && | 逻辑与 | 左结合 |
| 12 | | | 逻辑或 | 左结合 |
| 13 | ?: | 条件运算符 | 右结合 |
| 14 | = += -= *= /= %= <<= >>= &= ^= = | 赋值运算符 | 右结合 |
| 15 | , | 逗号运算符 | 左结合 |
补充说明:
后缀 ++ / – 的优先级高于前缀 ++ / –。
[]、()、.、-> 属于最高优先级。
sizeof 是一元操作符,优先级与 !、~ 等相同。
复合字面量 (type){…}(C99)优先级与后缀操作符相同,但此处不展开。
3. 结合性详解
3.1 左结合(从左到右)
大部分二元操作符都是左结合,包括:
算术:* / % + –
移位:<< >>
关系/相等:< <= > >= == !=
位操作:& ^ |
逻辑:&& ||
逗号:,
示例:
int a = 20 / 5 * 2; // (20 / 5) * 2 = 8,不是 20 / (5 * 2) = 2
int b = 1 << 2 << 1; // (1 << 2) << 1 = 8,不是 1 << (2 << 1) = 16
int c = 1 | 2 & 3; // 1 | (2 & 3) = 3,因为 & 优先级高于 |,同优先级时才看结合性
3.2 右结合(从右到左)
右结合的操作符包括:
一元操作符:前缀 ++ –、+ – ! ~、* &、(type)、sizeof、_Alignof
条件运算符:?:
赋值运算符:= 及复合赋值
示例:
int x = 5;
int *p = &x; // & 和 * 都是一元操作符,右结合
int y = *&x; // 相当于 *(&x),因为 & 和 * 优先级相同且右结合
int a = 1, b = 2, c = 3;
a = b = c; // 相当于 a = (b = c),右结合
int max = a > b ? a : b > c ? b : c;
// 条件运算符右结合:a > b ? a : (b > c ? b : c)
3.3 注意:函数参数中的逗号不是运算符
函数调用 f(a, b) 中的逗号是分隔符,不是逗号运算符,因此没有求值顺序保证。
printf("%d %d", i++, i++); // 未定义行为,不要这样写
4. 常见陷阱与易错点
4.1 *p++ 到底是 (*p)++ 还是 *(p++)?
后缀 ++ 优先级高于解引用 *,所以:
*p++ 等价于 *(p++)
含义:先取得 p 当前指向的值,然后指针 p 自增(指向下一个元素)。
如果你想先自增指向的值,应写:
(*p)++;
4.2 a & b == c 是逻辑错误
== 的优先级高于 &,所以:
a & b == c 等价于 a & (b == c)
通常本意是 (a & b) == c。务必加括号。
4.3 a << 2 + 1 的结果
- 的优先级高于 <<,所以:
a << 2 + 1 等价于 a << (2 + 1) = a << 3
如果你想先移位再加1,必须写 (a << 2) + 1。
4.4 a = b || c && d 的计算
&& 优先级高于 ||,所以:
a = b || c && d 等价于 a = (b || (c && d))
注意 || 和 && 有短路求值特性。
4.5 x & 1 == 1 的解析
== 高于 &,所以:
x & 1 == 1 等价于 x & (1 == 1) = x & 1
这恰好能得到“判断最低位是否为1”的效果,但语义不清晰,建议写 (x & 1) == 1。
4.6 赋值运算符的返回值
赋值表达式本身也有值,其值为赋值后左操作数的值。因为赋值右结合,可以链式赋值:
int a, b, c;
a = b = c = 5; // 相当于 a = (b = (c = 5))
但不要混淆:
if (a = 5) { ... } // 常见笔误,应写成 if (a == 5)
4.7 条件运算符的嵌套
条件运算符是右结合,可以嵌套,但可读性差:
int score = 75;
char *grade = score >= 90 ? "A" :
score >= 80 ? "B" :
score >= 70 ? "C" :
score >= 60 ? "D" : "F";
等价于:
char *grade;
if (score >= 90) grade = "A";
else if (score >= 80) grade = "B";
else if (score >= 70) grade = "C";
else if (score >= 60) grade = "D";
else grade = "F";
4.8 逗号运算符的优先级最低
int a = (1, 2, 3); // a = 3,因为逗号运算符左结合,依次求值,结果为最后一个
int b = 1, 2, 3; // 编译错误或 b = 1,因为逗号优先级低于赋值,解析为 (int b = 1), 2, 3
5. 记忆口诀
优先级从高到低可简单归纳为:
括后一,单目右,乘除加减,移位关系,相等位与,异或或,逻辑条件赋值逗号
更常见的是分段记忆:
1.括号、数组下标、成员访问、后缀 ++/–
2.一元操作符
3.乘除取模
4.加减
5.移位
6.关系
7.相等
8.按位与
9.按位异或
10.按位或
11.逻辑与
12.逻辑或
13.条件
14.赋值
15.逗号
结合性记忆:
右结合:所有一元操作符、条件运算符、赋值运算符。
左结合:其余二元操作符和逗号运算符。
6. 总结
优先级决定不同操作符之间的运算顺序。
结合性决定同优先级操作符的分组方向。
优先级和结合性只影响表达式的解析方式,不影响子表达式的求值顺序。
遇到不确定的表达式,加括号是最简单、最可靠的方式。
避免在一个表达式中多次修改同一个变量(如 i++ + ++i),这是未定义行为。
理解这些规则,阅读和编写C语言代码会更加清晰,也能避免很多隐蔽的bug。

