C++ 表达式求值优先级、结合律与求值顺序(五十九)
1. 运算符优先级与结合律
- 优先级(Precedence) 决定未加括号时运算符如何“绑”在一起:5 + 10 * 20 / 2; // 等同于 5 + ((10 * 20) / 2)
- 结合律(Associativity) 决定同级运算符的结合方向: - 左结合(大多数二元运算符):20 - 15 - 3→(20 - 15) - 3
- 右结合(赋值、条件、一些复合赋值运算符):a = b = c→a = (b = c)
 
- 左结合(大多数二元运算符):
技巧:遇复杂表达式,加括号明确意图。
2. 求值顺序与短路
- 默认情况下,C++ 并不保证子表达式的求值顺序——除非运算符明确规定。
- 短路求值: - &&:只有左侧为真时才求右侧
- ||:只有左侧为假时才求右侧
- ?::仅求选中的那分支
 
- 逗号运算符 ,:先求左侧(结果丢弃),再求右侧,返回右侧结果。
int x = (f1(), f2());  // 只调用 f1() 后再调用 f2()
3. 算术运算符
| 运算符 | 功能 | 结合律 | 
|---|---|---|
| +-(一元) | 符号取正/取负 | 右 | 
| */% | 乘、除、取余 | 左 | 
| +-(二元) | 加、减 | 左 | 
- 整数除法 向零截断(C++11 起)。
- 取余 遵循 (a/b)*b + a%b == a。
4. 关系与逻辑运算符
| 运算符 | 功能 | 结合律 | 
|---|---|---|
| <<=>>= | 关系比较 | 左 | 
| ==!= | 相等/不等 | 左 | 
| ! | 逻辑非 | 右 | 
| && | 逻辑与 | 左 | 
| ` | ` | 
注意:不能把布尔字面值 true/false 与非布尔表达式比较,易引发歧义。
5. 赋值与复合赋值
- 普通赋值 =:右结合,左侧必须是非常量左值,返回左值本身。
- 复合赋值 +=、-=、*=、|=、…:等价于a = a op b,但只评估一次a。
// 更安全、更高效
sum += val;
mask |= (1UL << bit);
6. 递增/递减运算符
| 形式 | 含义 | 求值结果 | 
|---|---|---|
| ++i | 前置:先加 1,再返回左值 | 左值 | 
| i++ | 后置:先返回旧值,再加 1 | 右值 | 
警惕 在同一表达式中同时修改和读取同一变量,会导致未定义行为!
7. 条件与逗号运算符
- 条件 cond ? A : B:右结合,只计算所选分支。
- 逗号 E1, E2:左结合,顺序求值,返回E2。
// 条件表达式须两边类型可兼容或可转换
auto grade = (score < 60 ? "Fail" : "Pass");// for 循环中同时更新两个变量
for (int i = 0, c = 100; i < 10; ++i, --c) { … }
8. 位运算符
| 运算符 | 功能 | 结合律 | 
|---|---|---|
| ~ | 按位取反 | 右 | 
| << | 左移 | 左 | 
| >> | 右移 | 左 | 
| & | 按位与 | 左 | 
| ^ | 按位异或 | 左 | 
| | | 按位或 | 左 | 
提示:用于掩码、标志位或快速乘除以 2 的幂。但要注意符号扩展与未定义行为。
9. sizeof 运算符
 
- 编译时 常量表达式,不触发构造/析构、函数调用或解引用。
- 返回 size_t:可用于数组维度、static_assert。
- 数组名不退化:sizeof arr == N * sizeof(arr[0])。
int a[10];
static_assert(sizeof a / sizeof a[0] == 10, "元素个数应为 10");
结语
透彻理解 C++ 的运算符优先级、结合律与求值顺序,能帮你:
- 避免未定义行为:防止同时修改与读取、写错括号导致逻辑混乱
- 提高性能和可读性:恰当运用复合赋值、短路逻辑、位运算
- 写出更健壮的模板代码:掌握 sizeof、常量表达式与static_assert
下次再碰到复杂表达式,请先画出运算符“优先级地图”,加上必要的括号,写出让人“一眼就懂”的 C++ 代码。祝你编码愉快!
