前言

问题:

#include <iostream>
using namespace std;

int main()
{
	int i = 2;
	i += ++i + i++;
	cout << i;
	return 0;
}

输出是什么?


优先级

详细的优先级:https://cppreference.cn/w/cpp/language/operator_precedence

这是一部分关于加法的:

优先级 格式 含义
1 a++ aa 先返回再自增
2 ++a aa 先自增再返回
3 a + b 加法
4 a += b 加法赋值

回到最开始的问题:i += ++i + i++。按照优先级,添加括号后:i += ((++i) + (i++))。加法运算是从左往右,所以先计算:(++i)。将 i 先自增,再返回。也就是 i 变成了 33(++i) 的返回值是 33。然后计算 (i++)。先返回再自增。返回值是 33i 自增为 44。然后计算 ((++i) + (i++)) 也就是 3+3=63+3=6。然后计算 i += (...),此时 i=4i=4((++i) + (i++)) 的值是 66。所以 i += ((++i) + (i++)) 的值是 1010。(?)

在本地运行:

结果是 1111

如果用自己定义的函数来实现自增呢?

int adrt(int & i)
{ // ++i 
	i += 1;   // 先自增 
	return i; // 再返回 
}
int rtad(int & i)
{ // i++
	int j = i; // 记录原值 
	i += 1;    // 自增 
	return j;  // 返回 
}

本地运行:

结果是 1010

怎么这样就是 1010 了?

DeepSeek 解释

你遇到的现象,核心在于运算符优先级 ≠ 求值顺序

  • 运算符优先级:决定表达式如何“分组”,即哪些运算符先结合,像加括号一样。
  • 求值顺序:决定分组后,每个子表达式在运行时按什么顺序计算。

你基于优先级推导的 (++i) + (i++) 计算过程没错,但这并不能决定整个表达式的最终求值顺序。

💻 原始代码:典型的未定义行为

i += ++i + i++; 的问题在于,它同时做了两件互相冲突的事:

  1. 对变量 i 进行赋值+= 操作)。
  2. 在赋值表达式的右侧,对同一个变量 i 进行了两次修改++ii++)。

C++ 标准规定,如果一个表达式里对同一个变量进行多次修改,且这些修改之间没有明确的顺序(即“无顺序”),那这个行为就是未定义的。

具体到这里:

  • ++i 的副作用(把 i 加 1)
  • i++ 的副作用(把 i 加 1)
  • += 赋值的副作用(把结果写回 i

这三者对 i 的修改,相互之间没有“先序于”的关系。编译器可以按任意顺序执行它们。因此,编译器在 i += ++i + i++; 这种表达式下,可以自由选择求值顺序,导致结果不确定。

你的本地环境(可能是较新版本的GCC)产生 11 只是无数种合法结果之一。这并不能说明代码是正确的,它依然是错误的、不可移植的。

🎭 自定义函数:为什么结果是确定的 10?

当你使用自定义函数 adrt(模拟 ++i)和 rtad(模拟 i++)时,情况彻底改变了。

代码 i += adrt(i) + rtad(i); 中的 adrt(i)rtad(i)函数调用

C++ 标准规定,在函数体执行之前,存在一个“序列点”。这意味着:

  1. 调用函数前,所有参数(这里是 i 的引用)的计算必须完成。
  2. 进入函数体后,函数内部的所有操作(如 i += 1)会一次性、完整地执行完毕
  3. 函数返回后,才会继续执行外层的其它操作(如加法 + 和赋值 +=)。

因此,整个表达式的执行顺序被强制固定为:

  1. 调用 adrt(i)i 变为 3,返回 3
  2. 调用 rtad(i),返回 3i 变为 4
  3. 计算 3 + 3 = 6
  4. 执行 i += 6,此时 i4,所以结果是 10

这个结果是确定的,因为函数调用引入了明确的顺序点,消除了所有不确定性。

💎 总结

  • i += ++i + i++;:是未定义行为,结果不可预测,11 只是编译器给出的一个可能的答案。
  • i += adrt(i) + rtad(i);:是行为确定的,结果为 10,因为函数调用引入了严格的顺序。

在编程中,永远不要编写像 i += ++i + i++; 这样试图在同一个表达式中对同一个变量进行多次修改的代码。这是C++中一个非常经典且危险的陷阱。