C 运算符优先级

在 C 语言中,运算符的优先级决定了表达式中运算符的执行顺序。

C 运算符优先级是什么?

所谓的优先级,也就是执行顺序的意思。我们都知道,数学中的加减乘除运算是有一定优先级的。比如有 “()” 就得先算 “()”,然后算 “乘除”,最后才算 “加减”。

在 C 语言中,运算符也是有优先级的。各种运算符优先级大小如下表所示。

C 运算符优先级
优先级 运算符 说明 结合方向
15(最高) () 小括号 从左到右
[] 中括号,用于数组下标
-> 获取结构体成员(指针)
. 获取结构体成员(非指针)
14 ! 逻辑非 从右到左
~ 按位取反
++、-- 自增、自减
+ 正号(求正数)
- 负号(求负数)
* 求值(指针相关)
& 求址(指针相关)
(类型名) 显式转换(强制类型转换)
sizeof 求所占字节数
13 * 乘法 从左到右
/ 除法
% 求余
12 + 加法 从左到右
- 减法
11 << 左移 从左到右
>> 右移
10 < 小于(比较) 从左到右
<= 小于等于(比较)
> 大于(比较)
>= 大于等于(比较)
9 == 等于(比较) 从左到右
!= 不等于(比较)
8 & 按位与 从左到右
7 ^ 按位异或 从左到右
6 | 按位或 从左到右
5 && 与(逻辑) 从左到右
4 || 或(逻辑) 从左到右
3 ? : 条件运算 从右到左
2 =、+=、-=、*=、/= 赋值运算 从右到左
1(最低) , 逗号运算 从左到右

对于同一优先级的运算符,它们运算顺序是由 “结合方向” 决定的,而不是由 “先后顺序” 决定的。比如 ++ 和 -- 处于同一优先级,然后它们的结合方向是从右到左,所以 ++i-- 等价于 ++(i--)。

C 语言的运算符非常多,优先级也是非常复杂,我们不需要去记忆所有的规则,而只需要关注常见运算符的优先级就可以了。

  • 对于算术运算来说,“乘除” 比 “加减” 优先级要高,另外 “求余” 和 “乘除” 的优先级相同。
  • 对于逻辑运算来说,非(!)>与(&)>或(||)。
  • 对于赋值运算来说,这些赋值运算符优先级都非常低,所以对于一个表达式来说,往往最后才是赋值操作。

示例 1:求余和乘除的优先级比较

#include <stdio.h>

int main(void)
{
    int a = 20 % 11 / 3;
    printf("%d", a);
    return 0;
}

运行结果如下。

3

分析:

% 和 / 的优先级是相同的,然后结合方向是从左到右,所以 20 % 11 / 3 等价于 (20 % 11) / 3。

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

#include <stdio.h>

int main(void)
{
    int a = 10, b = 0, c = 20;

    if (!a && b || c)
    {
        printf("Good");
    }
    else
    {
        printf("Bad");
    }

    return 0;
}

运行结果如下。

Good

分析:

对于 3 个逻辑运算符的优先级来说:非(!)>与(&)>或(||)。所以 !a && b || c 等价于 ( (!a) && b ) || c。

示例 3:比较运算符 vs 逻辑运算符

#include <stdio.h>

int main(void)
{
    int a = 10, b = 10, c = 20;

    if (a == b && c > a)
    {
        printf("Good");
    }
    else
    {
        printf("Bad");
    }

    return 0;
}

运行结果如下。

Good

分析:

比较运算符的优先级比逻辑运算符的要高,所以a == b && c > b 等价于 (a == b) && (c > b)。对于初学的小伙伴,如果不清楚优先级的话,还是建议加上 “()”,这样可以使得代码的可读性更高。

C 运算符优先级的最佳实践

在实际开发中,优先级可以很好地控制运算的执行顺序,但是如果仅仅依赖优先级,那么代码的可读性可能会很差,我们先来看一个例子。

示例 4:依赖运算符优先级

#include <stdio.h>

int main(void)
{
    int a = 10, b = 10, c = 20;

    if (!a == b && c > a || c < b)
    {
        printf("Good");
    }
    else
    {
        printf("Bad");
    }

    return 0;
}

运行结果如下。

Bad

分析:

对于 “与或非” 这 3 个的优先级来说:非(!)>与(&)>或(||)。另外比较运算符的优先级比 && 和 || 要高,但比 !(逻辑非)要低,所以 !a == b && c > a || c < b 其实等价于下面代码。

((!a == b) && (c > a)) || (c < b)

不仅仅是初学者,即使是对于我们这种工作多年的工程师来说,!a == b && c > a || c < b 这种代码的可读性是比较差的,有时并不能一下子就能看出来里面的执行顺序。在实际开发中,我们建议加上一些必要的 “()”,这样可以让代码的可读性更高。在大型项目中,代码的可读性和可维护性是非常重要的 2 个指标。

提示: 对于本例子使用到的 if 语句,我们在后面 “C if 语句” 一节中会详细介绍。

示例 5:自增自减的优先级

#include <stdio.h>

int main(void)
{
    int a = 10;
    int b = --a + 20;

    printf("%d", b);
    return 0;
}

运行结果如下。

29

分析:

从上表可知,“自增自减(优先级 14)” 高于 “加减运算(优先级 12)”,所以程序会先计算 --a,得到结果后再与 20 相加。因此下面 2 种方式是等价的。

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

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

方式 2 比方式 1 更加简单直观,可读性也更好,所以我们更推荐使用方式 2。对于自增自减运算符来说,我们比较建议把它单独拿出来,而不是放到某一个表达式中。

示例 6:赋值运算符的优先级

#include <stdio.h>

int main(void)
{
    int a = 10;

    a += a -= a * a;
    printf("%d", a);
    return 0;
}

运行结果如下。

-180

分析:

从 “运算符的优先级” 一表可以知道,赋值运算符的结合方向是 “从右到左”,所以 a += a -= a * a; 等价于下面代码。

a = a - a *a;    // 即 a = 10 - 10 * 10,也就是 a = -90
a = a + a;       // 即 a = (-90) + (-90),也就是 a = -180

实际上 a += a -= a * a; 这一句代码可读性也是非常差的,在实际工作开发中,我们也是非常忌讳使用这种方式。在多人协作的复杂项目中,你要是使用这种方式,估计团队老大要把你拖出来 “示众” 了。

最后需要说明的是,很多初学者的小伙伴喜欢用奇怪的语法来 “炫技”,故意把代码写得很难懂,其实这样是完全没有必要的。在真实的工作开发中,程序的性能速度、代码的可读性、代码可维护性等这些才是最重要的。在保证性能速度的情况下,能把代码写得越好懂,后面维护起来才越容易。

另外,语法本身就没有太多可以炫技的东西,对于多年开发的工程师来说,不管你用多难懂的语法来写,在别人眼里都只是小儿科而已。真正能体现你技术水平的应该是架构设计、算法设计、功能实现等,而不是三两句看起来很难懂的代码。

上一篇: C 运算符

下一篇: C 类型转换

给站长反馈

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

邮箱:lvyenet@vip.qq.com

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