运算符优先级与结合性:优先级表、结合性概念、如何避免优先级问题

说到运算符,很多初学者觉得不就是加减乘除嘛,有什么好讲的?

但我在项目中见过太多因为优先级搞错而引发的bug了。有一次排查一个通信协议栈的bug,整整花了两天,最后发现就是一行代码里 && 和 || 的优先级搞混了。嗯,从那以后,我每次写复杂表达式都会多看一眼优先级表。

一、运算符优先级表

C语言里运算符有几十个,优先级从高到低分了15个等级。你不需要全部背下来,但常用的那几个必须烂熟于心。

优先级 运算符 名称 结合性
1(最高)() [] -> .括号、数组下标、成员访问左→右
2! ~ ++ -- + - * & (类型) sizeof单目运算符右→左
3* / %乘除取模左→右
4+ -加减左→右
5<< >>移位左→右
6< <= > >=关系比较左→右
7== !=相等判断左→右
8&按位与左→右
9^按位异或左→右
10|按位或左→右
11&&逻辑与左→右
12||逻辑或左→右
13?:三目运算符右→左
14= += -= *= /= %= <<= >>= &= ^= |=赋值运算符右→左
15(最低),逗号运算符左→右

你想想看,优先级最高的其实是括号和成员访问,最低的是逗号运算符。这个顺序是有道理的——括号最优先,因为它要明确指定计算顺序;逗号最靠后,因为它只是把多个表达式串起来。

核心记忆口诀:
单目 > 算术 > 移位 > 关系 > 位运算 > 逻辑 > 三目 > 赋值 > 逗号

二、结合性的概念

优先级决定了谁先算,但遇到同一优先级的运算符时,谁先算?这就看结合性了。

说白了,结合性就是「当优先级相同时,计算方向是左到右,还是右到左」。

举个例子:

int a = 3, b = 4, c = 5;
int result = a - b - c;  
// 左结合:先算 a - b = -1,再算 -1 - c = -6

加减法是左结合,所以从左往右算。但赋值运算符是右结合:

int x, y, z;
x = y = z = 10;  
// 右结合:先算 z = 10,再算 y = z,最后 x = y

我记得刚学C语言时,看到 x = y = z = 10 这种写法觉得挺奇怪的。后来才明白,这就是右结合的实际应用——从右往左依次赋值。

个人经验: 我写嵌入式代码时,很少连续赋值。因为有些编译器优化会搞出奇怪的行为。建议一行只赋一个值,代码可读性更好。

三、如何避免优先级问题——用括号

你可能会问:优先级表这么长,我记不住怎么办?

我的回答很简单:记不住就别硬记,用括号。

括号的优先级是最高的,而且它能让代码意图一目了然。我个人的习惯是:只要表达式里用了两种以上不同的运算符,就加括号。

来看几个我踩过的坑:

坑1:移位和加减混用

// 错误写法
int val = 1 << 2 + 3;   // 你以为的是 (1<<2)+3 = 7
                        // 实际是 1 << (2+3) = 32

// 正确写法
int val = (1 << 2) + 3; // 明确告诉编译器先移位再加

为什么会这样?因为 + 的优先级(4级)比 <<(5级)高。所以先算加法,再移位。

坑2:逻辑运算符和比较运算符混用

// 我曾经在状态机里写过这样的代码
if (status & 0x02 == 0x02)  // 本意是判断第1位是否为1
    // 实际:== 优先级高于 &,所以先算 0x02 == 0x02 = 1
    // 再算 status & 1,结果完全不对!

// 正确写法
if ((status & 0x02) == 0x02)  // 先按位与,再比较
曾经踩过的坑: 我在一个产品代码里看到过 if (a & b == c) 这种写法,结果调试了整整一个下午。从那以后,我要求团队所有成员:位运算必须加括号,逻辑运算必须加括号,比较运算也建议加括号。

坑3:三目运算符嵌套

// 三目运算符是右结合,但可读性极差
int max = a > b ? a > c ? a : c : b > c ? b : c;

// 我建议拆开写,或者加括号
int max = (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c);
// 但说实话,这种嵌套三目我一般不用,直接 if-else 更清晰

四、知识体系总览

下面这张图把优先级、结合性、避坑方法串起来了,你可以保存下来随时看:

运算符优先级与结合性 · 知识体系 优先级表 15个等级,从高到低 结合性 左结合 vs 右结合 避坑方法 多用括号,少依赖记忆 常用优先级速记 1. 括号 () [] -> . 2. 单目 ! ~ ++ -- 3. 算术 * / % + - 4. 关系 < > == != 结合性示例 左结合:a - b - c → (a - b) - c 右结合:x = y = z → x = (y = z) 避坑三原则 ① 位运算加括号 ② 逻辑运算加括号 ③ 三目嵌套拆开写 核心原则 「优先级记大概,结合性看方向,复杂表达式一律加括号」 优先级 + 结合性 + 括号 = 安全可靠的表达式

五、总结几条实用建议

  • 记优先级不用死记硬背:记住「单目 > 算术 > 关系 > 逻辑 > 赋值」这个顺序就够了,其他查表。
  • 结合性只关注右结合的运算符:赋值、三目、单目是右结合,其他基本都是左结合。
  • 括号是你的朋友:我写代码时,只要表达式里用了两种以上运算符,就加括号。代码是给人读的,不是给编译器读的。
  • 代码审查时重点看表达式:我每次做code review,都会盯着那些没有括号的复杂表达式多看几眼。十有八九能发现问题。
我的习惯: 在嵌入式项目中,我甚至会在宏定义里加括号,哪怕看起来多余。比如 #define MUL(a,b) ((a)*(b)),外面再加一层括号,确保宏展开后不会出问题。这是血的教训换来的经验。

好了,优先级和结合性就讲到这里。记住一句话:括号不丢人,丢人的是bug查不出来。


公众号:蓝海资料掘金营,微信deep3321