当运算符开始“论资排辈”,你的代码还好吗?
亲爱的程序员朋友,你是否曾经写下 a = b + c * d,自信满满地按下编译键,结果程序跑出来的数字让你怀疑人生?
你是否在深夜调试时,盯着 if (x & y == z) 这样的代码,内心咆哮:“这TM到底先算谁?!”
别慌,你不是一个人!C语言的运算符优先级,就像一群性格迥异的大佬聚在一起开会——* 觉得自己的优先级天下第一,++ 在旁边冷笑:“呵,你算老几?”,而 = 只能卑微地等所有人吵完再干活……
本文将带你走进这场“运算符权力的游戏”,用最接地气的方式,帮你理清谁先算、谁后算,从此告别“优先级玄学”,让代码不再“薛定谔的正确”!
(友情提示:阅读本文后,你可能再也不会写出 a+++++b 这种让编译器崩溃的代码了……)
运算符类别 | 运算符 |
|---|---|
下标引用、函数调用和结构成员 | [] () -> . |
单目运算符 | ! ~ ++ -- + - sizeof (type) * & |
算术运算符 | + - * / % |
移位运算符 | << >> |
关系运算符 | > >= < <= == != |
位运算符 | & ^ | |
逻辑运算符 | && || |
赋值运算符 | = += -= *= /= %= &= ^= |= <<= >>= |
条件运算符(三目运算符) | expr1 ? expr2 : expr3 |
逗号运算符 | , |
C语言运算符优先级表(由上至下,优先级依次递减)
运算符 | 结合性 |
|---|---|
() [] -> . | L-R |
! ~ ++ -- + - (type) * & sizeof | R-L |
* / % | L-R |
+ - | L-R |
<< >> | L-R |
< <= > >= | L-R |
== != | L-R |
& | L-R |
^ | L-R |
| | L-R |
&& | L-R |
|| | L-R |
= += -= *= /= %= &= ^= |= <<= >>= | R-L |
expr1 ? expr2 : expr3 | R-L |
, | L-R |
L-R(Left-to-Right,从左到右结合) R-L(Right-to-Left,从右到左结合)
优先级最高的其实并不是真正意义上的运算符,包括数组下标、函数调用操作符和各结构体成员选择操作符。
单目运算符的优先级仅次于前述运算符,在所有真正意义是的运算符中,单目运算符的优先级最高。
优先级比单目运算符要低的,接下来就是双目运算符。
双目运算符之后就是三目运算符(条件运算符)。
优先级最低的就是逗号运算符了。

需要进一步细分的就是双目运算符了,我们需要记住:在双目运算符中,算术运算符的优先级最高,移位运算符次之,关系运算符再次之,接着就是位运算符,最后是逻辑运算符。
你们可能以为我忘了双目运算符中还有一个赋值运算符,其实赋值运算符算是一个特例:赋值运算符的优先级低于三目运算符(条件运算符)。

需要注意的最重要的两点是:
C语言运算符优先级与数学思维的完美对应
C语言中的算术运算符优先级设计完全符合我们的数学常识:
*)、除法(/)和取模(%)具有相同的高优先级+)和减法(-)优先级较低这种设计使得表达式a + b * c会先计算乘法后计算加法,与数学中的运算顺序完全一致,大大降低了学习成本。
在关系运算符中有一个需要特别注意的优先级规则:
>、>=、<、<=具有较高优先级==和!=的优先级相对较低这意味着表达式a > b == c < d实际上等价于(a > b) == (c < d),这种设计使得复合逻辑判断更加直观。
位运算符和逻辑运算符的优先级设计体现了严谨的逻辑层次:
&高于按位或|&&高于逻辑或||^的优先级介于按位与&和按位或|之间这种层次分明的优先级设计使得位操作表达式能够准确表达开发者的意图,例如a & b ^ c | d会按照(a & b) ^ c) | d的顺序计算。
int arr[] = {1, 2, 3};
int *p = arr;
int val = *p++; // 你以为是什么?问题分析:
(*p)++,实际是*(p++)++优先级高于*,且是右结合正确写法:
(*p)++; // 这才是解引用后自增int flags = 0x0F;
if (flags & 0x0F == 0x0F) { // 这个条件永远为假!
printf("All bits set\n");
}问题分析:
==优先级高于&,所以实际是flags & (0x0F == 0x0F)(0x0F == 0x0F)结果为1,所以实际是flags & 1正确写法:
if ((flags & 0x0F) == 0x0F)int x = 1 << 2 + 3; // 你以为结果是32?其实是128!解析:
+优先级高于<<,所以是1 << (2 + 3) = 1 << 5 = 32(1 << 2) + 3 = 4 + 3 = 7,就需要加括号int a = 1, b = 0;
if (a == 1 || b == 1 && some_expensive_function()) {
// 你以为some_expensive_function()不会执行?
}解析:
&&优先级高于||,所以等价于a == 1 || (b == 1 && some_expensive_function())a == 1为真,||短路,后面的确实不会执行多用括号:即使你知道优先级规则,加括号也能提高代码可读性
// 不推荐
a = b + c * d;
// 推荐
a = b + (c * d);分解复杂表达式:将复杂表达式拆分成多个简单语句
// 不推荐
result = (*ptr++) + (x << 2) & mask;
// 推荐
int val = *ptr;
ptr++;
int shifted = x << 2;
result = (val + shifted) & mask;编写单元测试:对涉及复杂运算符的代码编写测试用例
危险组合 | 常见误解 | 实际含义 | 解决方案 |
|---|---|---|---|
*p++ | (*p)++ | *(p++) | 明确加括号 |
a & b == c | (a & b) == c | a & (b == c) | 加括号 |
a << b + c | (a << b) + c | a << (b + c) | 加括号 |
a = b = c | 从左到右赋值 | 从右到左赋值 | 保持原样或拆分 |
运算符优先级就像交通规则,了解它们可以避免代码中的"交通事故"。记住:当你对优先级有哪怕一丝怀疑时,就加上括号吧!这不会降低你的专业度,反而会让你的代码更健壮、更易维护。
最后的小测试:你能一眼看出下面代码的问题吗?
int x = 5, y = 10, z = 15;
int result = x > y ? y : z + 10;答案在评论区见!