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++ 代码。祝你编码愉快!