- gf24240 的博客
关于运算符优先级
- @ 2026-8-26 12:55:43
前言
问题:
#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++ |
将 先返回再自增 |
| 2 | ++a |
将 先自增再返回 |
| 3 | a + b |
加法 |
| 4 | a += b |
加法赋值 |
回到最开始的问题:i += ++i + i++。按照优先级,添加括号后:i += ((++i) + (i++))。加法运算是从左往右,所以先计算:(++i)。将 i 先自增,再返回。也就是 i 变成了 ,(++i) 的返回值是 。然后计算 (i++)。先返回再自增。返回值是 ,i 自增为 。然后计算 ((++i) + (i++)) 也就是 。然后计算 i += (...),此时 ,((++i) + (i++)) 的值是 。所以 i += ((++i) + (i++)) 的值是 。(?)
在本地运行:
结果是
如果用自己定义的函数来实现自增呢?
int adrt(int & i)
{ // ++i
i += 1; // 先自增
return i; // 再返回
}
int rtad(int & i)
{ // i++
int j = i; // 记录原值
i += 1; // 自增
return j; // 返回
}
本地运行:
结果是
怎么这样就是 了?
DeepSeek 解释
你遇到的现象,核心在于运算符优先级 ≠ 求值顺序。
- 运算符优先级:决定表达式如何“分组”,即哪些运算符先结合,像加括号一样。
- 求值顺序:决定分组后,每个子表达式在运行时按什么顺序计算。
你基于优先级推导的 (++i) + (i++) 计算过程没错,但这并不能决定整个表达式的最终求值顺序。
💻 原始代码:典型的未定义行为
i += ++i + i++; 的问题在于,它同时做了两件互相冲突的事:
- 对变量
i进行赋值(+=操作)。 - 在赋值表达式的右侧,对同一个变量
i进行了两次修改(++i和i++)。
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++ 标准规定,在函数体执行之前,存在一个“序列点”。这意味着:
- 调用函数前,所有参数(这里是
i的引用)的计算必须完成。 - 进入函数体后,函数内部的所有操作(如
i += 1)会一次性、完整地执行完毕。 - 函数返回后,才会继续执行外层的其它操作(如加法
+和赋值+=)。
因此,整个表达式的执行顺序被强制固定为:
- 调用
adrt(i),i变为3,返回3。 - 调用
rtad(i),返回3,i变为4。 - 计算
3 + 3 = 6。 - 执行
i += 6,此时i是4,所以结果是10。
这个结果是确定的,因为函数调用引入了明确的顺序点,消除了所有不确定性。
💎 总结
i += ++i + i++;:是未定义行为,结果不可预测,11只是编译器给出的一个可能的答案。i += adrt(i) + rtad(i);:是行为确定的,结果为10,因为函数调用引入了严格的顺序。
在编程中,永远不要编写像 i += ++i + i++; 这样试图在同一个表达式中对同一个变量进行多次修改的代码。这是C++中一个非常经典且危险的陷阱。