C++ 的求值顺序(五十一)

一、为什么大多数表达式“先后顺序”不固定?

考虑这样一行代码:

int i = 0;
std::cout << i << " " << ++i << std::endl;  //  ❓ 未定义行为!
  • 运算符 <<优先级结合律告诉编译器应当如何“分组” ((std::cout << i) << " ") << ++i ...
  • 但它们并不告诉编译器:“先计算 i 吗?还是先计算 ++i?”

在 C++11/14 之前,编译器可以自由决定对左右操作数的求值先后。当同一对象既被读又被写,又没有规定先后,就会出现 未定义行为

编译器可能先算 i 输出 0,再算 ++i 输出 1(得到 0 1);也可能先算 ++i 输出 1,再算 i 输出 1(得到 1 1);甚至更“花样”都可能出现。

二、哪几种运算符“有承诺”?

C++ 仅有种运算符保证了对操作数的求值顺序

运算符顺序规则
逻辑与 &&先求 左边,只有左边为 true 时才求右边
逻辑或 `
条件运算符 ?:先求 条件,再决定求“真”分支还是“假”分支
逗号运算符 ,先求 左侧,再求 右侧
if (computeA() && computeB()) { /*…*/ }
// computeA() 一定先调用,如果返回 false 则 computeB() 不被调用

int x = cond ? computeTrue() : computeFalse();
// cond 一定先求值,只有一条分支被调用

这四种运算符是少有的“define sequence”点,开发者可以借此控制副作用的顺序。

三、C++17:更严格的求值规则(“顺序点”升级)

C++17 对许多此前未定义的求值顺序做了明确指定

  1. 函数调用
    • 实参自左向右求值
  2. 赋值运算
    • 右侧先求值,再将结果赋给左侧
  3. 逗号运算符已提,仍然是左到右
  4. 初始化列表中的元素自左向右求值
// C++17 起,以下行为都有定义:
foo( a(), b(), c() );    // a() → b() → c() → 调用 foo
x = y = z();             // z() → (y = z()) → (x = y)
auto v = { p(), q(), r() }; // p() → q() → r()

对于链式 operator<<(I/O 连写)、混合了读写同一对象的算术/关系运算,依然未定义

核心原则: 要在一个表达式里,对同一对象做多次读写,除非中间有明确的序列点(sequence point)。

四、常见误区与陷阱

  1. 链式 I/O 异步副作用
    std::cout << i << ++i; // 读写同一对象,无定义!
    
  2. 混合算术副作用
    int x = 1;
    x = x++ + 2;  // 未定义,不要这样写!
    
  3. 函数参数里读写全局/静态
    int g = 0;
    f(++g, g++);  // 未定义,两个都调用 g 的自增
    
  4. 复合赋值 & 下标
    arr[i] = i++; // i 的自增与数组写入冲突
    

五、最佳实践

  • 单一副作用:一个表达式中最多修改(写入)一个可变对象。
  • 逻辑分离:将副作用分散到多个语句中,避免“读写竞速”。
  • 善用顺序点:逻辑与、逻辑或、三元运算符、逗号运算符,或人为插入括号和临时变量,明确先后。
  • 升级到 C++17:新标准帮你管了一些函数调用的先后,但并不万金油。
  • 多做静态分析:开启编译器警告(-Wall -Wextra),借助 clang-tidycppcheck 检测潜隐风险。

🔑 牢记:

“先算哪个子表达式”并不是由优先级结合律决定的!
它们只告诉你“怎样分组”;真正的先后顺序只有四个运算符和 C++17 的部分新增规则才做了保证,其余情况全 不做承诺

理解并尊重求值顺序,你的 C++ 代码才不会成为“未定义行为的温床”。欢迎在评论区一起探讨更多细节!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hello.Reader

请我喝杯咖啡吧😊

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值