C++ 运算符优先级

当一条表达式里同时出现多个运算符号时,比如 2 + 3 * 4,程序到底会从左往右算出 20,还是先乘后加算出 14 呢?

实际结果是 14。这和我们平时数学里的四则运算是一样的:乘号(*)的级别比加号(+)高,所以要先算乘法、后算加法。在 C++ 中,这种决定各个运算符谁先算、谁后算的规则,就叫做 “运算符优先级”。

常见运算符优先级

对于前面我们已经学过的常用运算符,它们的优先级从高到低如下表所示。

常见运算符优先级
优先级 运算符 说明 结合方向
1(最高) () 小括号分组 从左到右
2 !、++、--、+、- 逻辑非、自增自减、正负号 从右到左
3 *、/、% 乘、除、求余 从左到右
4 +、- 加、减 从左到右
5 >、>=、<、<= 大于、大于等于、小于、小于等于 从左到右
6 ==、!= 等于、不等于 从左到右
7 && 逻辑与 从左到右
8 || 逻辑或 从左到右
9(最低) =、+=、-=、*=、/=、%= 赋值与复合赋值 从右到左

需要注意的是,表中第 2 级的 + 和 - 指的是写在单个数字或变量前面的 “正负号”,比如 -5、-a,只作用于一个操作数,而不是 a - b 里的减法。

C++ 的运算符很多,这张表只是方便小伙伴们平时查询,并不是叫大家死记硬背。在实际写代码时,我们只需要记住以下 4 条基本规律就够用了:

  • 小括号最高:小括号 () 的优先级最高,想让哪一部分先算,直接用小括号括起来即可。
  • 算术高于比较:乘、除、求余的优先级相同,且都高于加和减。所有算术运算都会在比较大小之前先算完。
  • 比较高于逻辑:先通过 >、== 等比较出真假,再用逻辑运算符组合。在 3 个逻辑运算符中,非(!)高于与(&&),与(&&)高于或(||)。
  • 赋值运算最低:赋值运算符(=、+= 等)的优先级最低,总是等号右边的表达式全部算完之后,最后才把结果赋给左边的变量。

示例 1:算术运算与小括号

#include <iostream>

int main() {
    int result1 = 2 + 3 * 4;
    int result2 = (2 + 3) * 4;

    std::cout << result1 << '\n';
    std::cout << result2 << '\n';
    return 0;
}

运行结果如下。

14
20

分析:

计算 2 + 3 * 4 时,* 的优先级高于 +,程序会先算 3 * 4 得到 12,再算 2 + 12 得到 14,最后赋给 result1。而在 (2 + 3) * 4 中,程序会优先计算小括号内的 2 + 3 得到 5,再乘以 4 得到 20。

如果一条表达式里同时混用了比较和逻辑运算符,计算顺序又是怎样的呢?下面我们再来看一个例子。

示例 2:比较与逻辑运算的优先级

#include <iostream>

int main() {
    int age = 16;
    bool hasTicket = false;
    bool isStaff = true;

    bool result1 = age >= 18 && hasTicket || isStaff;
    bool result2 = age >= 18 && (hasTicket || isStaff);

    std::cout << result1 << '\n';
    std::cout << result2 << '\n';
    return 0;
}

运行结果如下。

1
0

分析:

按照 “比较高于 &&,&& 高于 ||” 的规则,计算 result1 时相当于执行 ((age >= 18) && hasTicket) || isStaff。由于 age 是 16,前半部分为 false,而右边的 isStaff 为 true,两者做 || 运算的结果为 true,因此输出 1。

计算 result2 时,小括号强制让 (hasTicket || isStaff) 先算出 true,而左边的 age >= 18 为 false,两者做 && 运算的结果为 false,因此输出 0。编写 if...else 语句的条件判断时,合理加上小括号能让判断逻辑清晰很多。

运算符的结合方向

到这里小伙伴可能会问:如果一条表达式里出现的两个运算符优先级一模一样,比如求余(%)和除号(/)同属第 3 级,此时又应该先算哪一个呢?

当同一优先级的运算符放在一起时,计算顺序由 “结合方向”(也叫结合性)来决定,主要分为以下 2 种情况:

  • 从左到右结合:大多数常用的双目运算符,比如加减乘除、比较运算以及 && 和 ||,都是从左往右依次计算的。
  • 从右到左结合:主要是赋值运算符(=、+=、-= 等)以及单目运算符(!、正负号等),它们是从右往左依次计算的。

示例 3:运算符的结合方向

#include <iostream>

int main() {
    // 算术运算:从左到右结合
    int num = 20 % 11 / 3;

    // 赋值运算:从右到左结合
    int a = 0;
    int b = 0;
    a = b = 10;

    std::cout << num << '\n';
    std::cout << a << ' ' << b << '\n';
    return 0;
}

运行结果如下。

3
10 10

分析:

在计算 20 % 11 / 3 时,% 和 / 的优先级相同,且结合方向是 “从左到右”。因此程序会先算左边的 20 % 11 得到余数 9,再算 9 / 3 得到 3。

再看下面的连续赋值 a = b = 10;:赋值运算符(=)的结合方向是 “从右到左”,因此这行代码等价于先执行右边的 b = 10;,再把 b 的值赋给左边的 a:

b = 10;    // 先将 10 赋给 b
a = b;     // 再将 b 的值赋给 a

编写复杂表达式的建议

在实际开发中,如果写代码时过度依赖默认优先级,写出来的长表达式往往让人看得很累,还容易埋下隐患。建议大家平时养成以下 2 个习惯。

1. 多用小括号明确分组

当一条表达式中包含多个比较或逻辑运算符时,哪怕默认优先级本身是对的,我们也建议手动加上小括号 () 来明确分组。比如对比下面这两种等价写法:

// 方式 1
bool pass = !isBlocked && score >= 60 || isVip;

// 方式 2(推荐)
bool pass = ((!isBlocked) && (score >= 60)) || isVip;

不仅仅是初学者,就算是工作多年的开发工程师,看到方式 1 这种不加括号的长串条件,有时也得停下来反应一会儿。而在方式 2 中加上必要的括号后,哪两个条件是一组一目了然,后期维护起来也不容易出错。

提示 小伙伴们遇到拿不准优先级的表达式时,直接加上小括号 () 就可以了。小括号是在编译阶段确定计算顺序的,完全不会降低程序的运行效率。

2. 自增与自减单独成行

对于自增(++)、自减(--)以及复合赋值(+=、-=),我们建议把它们单独拆成一行语句,不要塞进复杂的算术表达式里。请看下面例子。

示例 4:拆分包含自减的表达式

#include <iostream>

int main() {
    int a = 10;
    int b = --a + 20;
    std::cout << b << '\n';
    return 0;
}

运行结果如下。

29

分析:

由于前置自减(第 2 级)的优先级高于加法(第 4 级),程序会先算 --a 让 a 变成 9,然后再与 20 相加得到 29。虽然合在一行也能算出结果,但像下面方式 2 这样拆成两行写会直观得多:

// 方式 1
int b = --a + 20;

// 方式 2(推荐)
--a;
int b = a + 20;

更需要特别注意的是:千万不要在同一条表达式里对同一个变量多次自增或自减,比如写出 int x = ++a + a++;。这在 C++ 中属于 “未定义行为”,不同编译器算出的结果可能完全不同。

不少初学者喜欢写看起来很绕的语法来 “炫技”。其实在多人协作的项目开发中,非常忌讳这种难读懂的写法,把代码写得直观清晰、方便维护才是最重要的。

上一篇:

下一篇:

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号