一文搞懂C++常见运算符优先级


对于初学 C++ 的同学来说,第一个让人混乱的便是 C++ 中复杂的算术表达式。下面是一个简单的例子:

  • 例题1:
    int a = 1;
    int b = 2;
    int c = 3;
    int d = 4;
    int e = 5;
    
    int result = a + b * c / d - e; //result = -3
    
  • 例题2:
    int a = 5;
    int b = 2;
    int c = ++a + b++ * 2; //c = 10
    int d = a++ + (++b) * 2; //d = 14
    

看完结果之后你可能不相信我写的答案,没关系!实践出真知,下面为运行结果

看看你的猜测是否正确?如果你能正确并完整地说出代码的运算顺序和结果,那么恭喜你,至少掌握了基本的运算规律😎


结论

嘿,看到这里了,你可能会好奇,我们应该如何理解表达式的运算顺序呢🤔?
先说结论,在我们常见的算术表达式运算中:

后置递增/减 优先运算,其次是 前置递增/减,最后是四则运算(加减乘除)。其中,四则运算的顺序与我们小学数学的顺序一样,遵循先乘除后加减

其次,若表达式中有括号的,先计算括号内的表达式


前置&后置递增和递减

觉得很难理解?没关系,下面我们使用代码来验证和加深理解

//首先我们先看递增/递减,这里我们统一以递增为例
//1.前置递增
int a = 10;
++a;  //让变量+1
cout << "a = "<< a << endl;  //11
   
//2.后置递增
int b = 10;
b++;  //让变量+1
cout << "b = " << b << endl;  //11

看到这里,你不经会问,这不都是一样的吗?有什么区别?你的问题非常好,思考的很全面。单独使用的时候确实没什么区别,但是放在表达式中可就不一样了

//你能说出b和c的值吗?
//假如把 a = 5 这行代码去掉,那么b和c的值会变化吗?
int a = 5;
int b = ++a + 2;
a = 5;
int c = a++ + 2;

我们先不看答案,先来看递增的规律:

  • 前置递增:先让变量+1 然后进行表达式的运算
  • 后置递增:先进行表达式运算,后让变量+1

那么读到这里,你是不是以及开始尝试求解了?我们一起来看看思路和运算顺序

int a = 5;

//首先看这个式子,因为++符号在变量名前面,所以是前置递增
int b = ++a + 2;
//根据前置递增的运算规则,先让变量+1 然后进行表达式的运算。给出如下运行步骤
1. a先自增1,那么现在a = 6
2. 自增后的a = 6再与2相加
3. 故 b = 8

看到这里,你会觉得 前置递增++aa = a + 1 的逻辑很相似。确实,你可以这么理解,但是这种理解仅限于 前置递增/减!至于为什么,我们再来看下面的解析

int a = 5;
int b = ++a + 2;

//把a的值重新置为5
a = 5;
//因为++符号在变量名前面,所以是后置递增
int c = a++ + 2;
//根据后置递增的运算规则,先进行表达式运算,后让变量+1。给出如下运行步骤
1. 因为是先进行表达式运算,那么先执行 a + 2
2. 那么此时 c = a + 2,故 c = 7
3. 最后a自增1,那么现在a = 6

假如你这时候还认为既然 ++a相当于a = a+1,那么 a++ 也相当于 a = a+1 的思路来看待这个问题就大错特错了🦆,为什么?我们接下来再看看

//你在编译器里打出这几行行代码对比一下
int a = 5;
cout << a++ << endl;	//a = 5
a = 5;
cout << (a = a + 1) << endl;	//a = 6

你可以看到,结果是不是完全和我们想象的不一样?a++ 居然输出的结果是5不是6?而 a = a+1 却输出的是6,这段代码就充分说明了为什么 a++ 相当于 a = a+1 这个思路的不正确性

这时细心的同学可能会发现,为什么每次我做完操作之后,多一行 a = 5

这是个非常棒的问题,说明你注意到了这行代码的特殊性。还是如此,我们解释原理步骤,再运行代码

假设我把 a = 5 这行代码注释了,我们再来看b和c的值

int a = 5;
//前置递增,变量先加一,再做表达式运算
int b = ++a + 2;	//a先加1变成6,然后再参与加2的运算,b = 8
//a = 5;
//后置递增,先进行表达式运算,后让变量+1
int c = a++ + 2;	//此时a的值为6,先参与运算加2,把结果赋值给c,c = 8
					//然后a再加1变成7

对比一下,你可以发现,c的值在变化。原因是 不管前置还是后置递增或递减,最后都要把原本的值覆盖

也就是说 不论是 a++还是++a,a的值最后都要被覆盖,只是改变的时机不一样罢了,这也就导致了c的值在变化的原因

看到这里,我想你应该明白了 前置&后置递增与递减 的特殊性


前置&后置递增和递减出现在同一表达式的优先级

接下来我们讨论 当表达式中出现同优先级的情况,以下是代码示例:

int a = 5;
int b = 2;
int c = a++ + b++; //c = 7
a = 5;
b = 2;
int d = a++ + b++ + --a; //d = 10

这是运行的结果

有了前面的知识铺垫之后,我们再看这个表达式是不是就更轻松了一点?

当然,你的答案可能会与结果不同,没关系,我们还是一起来看看他的运算顺序到底是怎么样的

首先我需要了解C++运算符的 结合性,什么是结合性?

结合性是指当一个表达式中有多个相同优先级的运算符时,它们的计算顺序是从左到右还是从右到左,左结合是指从左到右计算,右结合是指从右到左计算

  1. 例如,赋值运算符= 是右结合的,所以a = b = c相当于a = (b = c),也就是说,先把c的值赋给b,然后再把b的值赋给a
  2. 例如,加法运算符+ 是左结合的,所以a + b + c相当于(a + b) + c,也就是说,先把a和b相加,然后再和c相加

通过上述这两个例子,我们再次回看最简单的代码:

int a = 5;
int b = ++a + 2;
a = 5;
int c = a++ + 2;

根据结合性理论,我们可以看出:

  1. 前置递增/递减 属于 右结合
  2. 后置递增/递减 属于 左结合

其实我个人认为,上述的叫法会让初学者误认为,左/右结合 是 向左/右结合

其实真正应该叫 从左(向右)结合 / 从右(向左)结合更为恰当

也就是说,我们应该把 左结合与右结合 称作 从左结合与从右结合,这样避免了歧义,而且先前的叫法本身也不够严谨

int b = ++a + 2
      = (a = a + 1) + 2
      = (a = 5 + 1) + 2
      = 6 + 2
      = 8
a的值变化:5->6->参与表达式计算
---------int a = 5------------
int c = a++ + 2
      = a + 2
      = 5 + 2
      = 7
a的值变化:5->参与表达式计算->6

有了上述的理论基础之后我们再回头看这个代码:

int a = 5;
int b = 2;
int c = a++ + b++; //c = 7
/*
	1. 先算后置递增,c = a + b = 7
	2. 再算自增:a + 1, b + 1
*/
a = 5;
b = 2;
int d = a++ + b++ + --a; //d = 12
/*
	1. 先算后置递增,a = 5 + 1 = 6
	2. 再算前置递减:a = 6 - 1 = 5
	3. 再开始计算表达式:d = a + b + a = 5 + 2 + 5 = 12
*/

答案和过程是不是显而易见?但是!其实在C++中并不推荐 在表达式中多次修改同一个变量的值。这样容易导致表达式的行为不确定

比如,这段代码我在 Microsoft Visual Studio 2022 Community 上运行的结果为 d=10,但在线编译网站和 Dev Cpp 上运行的结果为 d=12

我也很不解,所以我发送了反馈提交给了VS的社区 Inconsistent results of C++ post-increment operator in different compilers,发现了相似的问题,微软官方给出的回复是:感谢您的反馈。但这是故意的。这是未定义的行为。根据标准,表达式中的操作序列是不确定的,模仿GCC/Clang的未定义行为解决方案不是MSVC的目标

根据 C++ 标准,表达式的计算顺序是未定义的(Undefined Behavior),也就是说,编译器可以自由地决定表达式中各个操作数的计算顺序。这就意味着,在不同的编译器或编译器设置下,对于同一个表达式,得到的结果可能是不同的,甚至是不可预测的

因此,我们应该:

  1. 不要在表达式中多次修改同一个变量的值。这样容易导致表达式的行为不确定
  2. 避免嵌套修改同一个变量的值的操作。例如,a = b = c = 0 这样的操作会使 a、b、c 依次被赋值为 0,但赋值的顺序是未定义的
  3. 在表达式中显式指定计算顺序。可以使用括号或者将表达式分解为多个语句来指定计算顺序

知乎中的一篇文章也指出了这个问题:关于C,C++表达式求值顺序


四则运算

在 C++ 中,四则运算的优先级遵循常见的数学规则,乘法和除法的优先级高于加法和减法。在进行四则运算时,可以使用括号来显式指定计算的优先级

以下是 C++ 中四则运算的优先级列表,从高到低:
在这里插入图片描述

回到开头的例子,我们按照这个表格的优先级计算结果

int a = 1;
int b = 2;
int c = 3;
int d = 4;
int e = 5;

int result = a + b * c / d - e;
1. 先计算 b*c = 2*3 = 6
2. 后计算 a + 6 / d - e
3. 再计算 a + 1 - e
4. 结果为:result = 1 + 1 - 5 = -3

由于四则运算 (加减乘除) 的结合性都是 从左向右 的,因此,当出现同一优先级但是顺序不同时,一般都按照 从左到右的顺序顺序进行运算,例如:

int a = 10;
int b = 5;
int result = a / b * 3;
5. 先计算 a/b = 10/5 = 2
6. 后计算 2 * 3
7. 结果为:result = 2*3 = 6

至此,我们常见的运算符优先级就讲到这里了,相信你看完一定有所收获😘,如果有什么问题可以私信或者评论区留言,我看到之后都会回复的

希望这篇文章能让你对C++中运算符优先级有更进一步的认识🤗


例题检测 (理解后可以直接到这里做题)

下面是几个关于 前置&后置递增与递减 的C++例题,希望能更进一步帮助你加深对概念的理解,答案在文章的末尾

题目一:
int a = 3;
int b = ++a + 2;
int c = a++ + 2;

题目二:
int a = 6;
int b = a++ * 3;
a = 6;
int c = --a * 3;

题目3int a = 6;
int b = ++a - 2;
int c = a++ / 8;
int d = --a * 3;
int e = a++ + 2;

题目4int a = 5;
int b = 6;
int c = (a++) * (++b) + 2;
int d = (++a) * (b--) + 2;


题目5int a = 8;
int b = 9;
int c = ++a - (b--) / 2;
int d = a-- - (++b / 2);

题目6
int a = 7;
int b = 8;
int c = (a--) * (b++) + 2;
int d = (--a) * (--b) + 2;

题目1:b = 6, c = 6
题目2:b = 18, c = 15
题目3:b = 5, c = 0, d = 21, e = 9
题目4:c = 37, d = 51
题目5:c = 5, d = 5
题目6:c = 58, d = 42

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐