为什么该C++示例中std::cout链式输出得01、分行输出得00?
差异本质是C++旧标准下链式流表达式未强制指定求值顺序,结合b()函数对类成员的修改副作用,共同导致了不同书写形式下的输出偏差。
单行输出语句输出01的逻辑
初始状态梳理
- main函数中构造A类实例后,构造函数初始化成员数组为
a.a[0] = 1、a.a[1] = 0。 - 首次调用
a.b()时,函数交换两个数组元素并返回原a[0]的值1,执行完成后成员数组状态更新为a.a[0] = 0、a.a[1] = 1。
- main函数中构造A类实例后,构造函数初始化成员数组为
链式表达式的本质
代码std::cout << a.b() << a.a[1];是链式双目运算符调用,等价于两次独立的operator<<函数执行:operator<<( operator<<(std::cout, a.b()), a.a[1] )。旧标准下的求值规则
在C++17及之前的标准中,函数调用的实参、以及被调函数本身的表达式求值顺序,没有统一强制规定,编译器可自由安排操作数的求值先后,只要保证操作数的所有副作用在函数正式执行前完成即可。编译器的实际执行路径
你使用的编译器针对这行代码,选择的求值顺序为:- 先对右侧操作数
a.a[1]求值:读取当前a[1]的值为1,暂存到输出队列中 - 再对左侧操作数
a.b()求值:进入b()函数,读取当前a[0]的值为0,交换数组元素后返回0,此时数组被修改为a.a[0] = 1、a.a[1] = 0
最终cout按照固定的输出顺序,依次输出队列中的值:先输出a.b()的返回值0,再输出之前暂存的a[1]旧值1,拼接结果为
01。- 先对右侧操作数
拆分两行输出语句输出00的逻辑
拆分为两行独立语句后,代码执行遵循严格的从上到下顺序,不存在编译器自由调度求值顺序的空间:
- 第一行执行
std::cout << a.b():完整跑完b()函数逻辑,读取当前a[0]的值0,交换数组元素后返回0并直接输出,此时成员数组状态为a.a[0] = 1、a.a[1] = 0。 - 第二行执行
std::cout << a.a[1]:直接读取当前时刻的a[1]值0并输出,拼接结果为00。
注:你拆分代码时书写的
std::cout << a[1];属于笔误——main中的类实例变量名为a,内部数组成员需要通过a.a[1]访问,直接写a[1]会触发编译错误。
C17对流运算符的求值顺序做了强制修正:规定链式流输出表达式中,左操作数的求值必须早于右操作数的求值。如果使用C17及以上标准编译原单行代码,求值顺序会被强制为先执行a.b()、再读取a.a[1],输出结果会和拆分两行的结果完全一致为00。你当前看到的01输出,是编译器默认采用C++17之前的编译规范、未强制指定求值顺序时产生的非确定结果。
内容的提问来源于stack exchange,提问作者Yurii Melnychuk

