在 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; 这一句代码可读性也是非常差的,在实际工作开发中,我们也是非常忌讳使用这种方式。在多人协作的复杂项目中,你要是使用这种方式,估计团队老大要把你拖出来 “示众” 了。
最后需要说明的是,很多初学者的小伙伴喜欢用奇怪的语法来 “炫技”,故意把代码写得很难懂,其实这样是完全没有必要的。在真实的工作开发中,程序的性能速度、代码的可读性、代码可维护性等这些才是最重要的。在保证性能速度的情况下,能把代码写得越好懂,后面维护起来才越容易。
另外,语法本身就没有太多可以炫技的东西,对于多年开发的工程师来说,不管你用多难懂的语法来写,在别人眼里都只是小儿科而已。真正能体现你技术水平的应该是架构设计、算法设计、功能实现等,而不是三两句看起来很难懂的代码。
