运算符优先级对照
选一门语言查它的运算符优先级表,或者搜一个运算符,看它在各语言里分别排在第几层、怎么结合。
层级序号只在同一门语言内部可比,所以展示时写的是「第几层 / 共几层」。搜中文名称也能命中,比如输入「幂」会找到所有语言里的幂运算。
分档很细,日常最该记住的是「比较高于位运算」和「?? 不能和 && / || 不加括号混用」。
来源:MDN《运算符优先级》。命中 0 层,按层级从高到低排列。
| 层级 | 运算符 | 名称 | 结合性 | 说明 |
|---|---|---|---|---|
| 1 / 20 | ( ) | 分组 | 不结合 | 括号可以改变任何其他层的顺序,是唯一绝对安全的手段 |
| 2 / 20 | a.b a[b] new a() a?.b | 成员访问与调用 | 左结合 | 可选链 a?.b 和普通成员访问同级,遇到 null 就短路 |
| 3 / 20 | new a | 无参数 new | 右结合 | 和 new a() 差一层,所以 new a().b 的语义和 new a.b 完全不同 |
| 4 / 20 | a++ a-- | 后置自增自减 | 不结合 | 先取值再自增,写在表达式里很容易读错 |
| 5 / 20 | !a +a -a ~a typeof void delete await | 一元运算 | 右结合 | typeof 和 delete 也是一元运算符,连写时从右往左算 |
| 6 / 20 | ** | 幂 | 右结合 | 右结合,2 ** 3 ** 2 等于 2 的 9 次方;一元负号要加括号,-2 ** 2 是语法错误 |
| 7 / 20 | * / % | 乘除取余 | 左结合 | 浮点取余会保留符号,-5 % 3 得 -2 |
| 8 / 20 | + - | 加减 | 左结合 | 只要有一边是字符串就变成拼接,这是最常见的类型陷阱 |
| 9 / 20 | << >> >>> | 位移 | 左结合 | 先把操作数转成 32 位整数,超过 32 位的数会被截断 |
| 10 / 20 | < <= > >= instanceof in | 关系 | 左结合 | in 会沿原型链查找,判断自有属性要用 Object.hasOwn |
| 11 / 20 | == != === !== | 相等 | 左结合 | == 会做类型转换,工程代码里一律用 === |
| 12 / 20 | & | 按位与 | 左结合 | 比相等低,所以 a & b == c 实际是 a & (b == c) |
| 13 / 20 | ^ | 按位异或 | 左结合 | 同样低于相等,容易和「判断不等」的直觉搞混 |
| 14 / 20 | | | 按位或 | 左结合 | 低于相等和位与 |
| 15 / 20 | && | 逻辑与 | 左结合 | 短路求值,返回的是操作数本身而不是布尔值 |
| 16 / 20 | ?? | 空值合并 | 左结合 | 只在 null 或 undefined 时取右边;不能和 && / || 不加括号混用,否则直接语法报错 |
| 17 / 20 | || | 逻辑或 | 左结合 | 比 ?? 低一层,a || b ?? c 是语法错误 |
| 18 / 20 | ?: | 条件运算符 | 右结合 | 右结合,a ? b : c ? d : e 等价于 a ? b : (c ? d : e) |
| 19 / 20 | = += -= *= /= %= <<= >>= &= ^= |= &&= ||= ??= | 赋值 | 右结合 | 右结合,a = b = c 会从右往左赋值 |
| 20 / 20 | , | 逗号 | 左结合 | 优先级最低,只在 for 语句和极少数字面量里用,别在表达式里用它串联 |
使用说明
运算符优先级描述的是一串没有括号的表达式该按什么顺序归约。同一门语言里每个运算符都落在某一层,层号越小越先算:乘除在加减之上,加减在比较之上,比较在逻辑运算之上,赋值和逗号压在最底下。这张表把每门语言最高的一层编成 1 往下排,但层级编号只在语言内部有效——JavaScript 分了 20 层、Go 连一元层算上也只有 6 层,层数差异反映的是分档粗细,不能拿一个语言的「第 5 层」和另一个语言的「第 5 层」直接比。这也是页面统一显示「第几层 / 共几层」的原因。
结合性管的是同一层里的多个运算符谁先算。左结合的 a - b - c 等于 (a - b) - c,右结合的 a = b = c 等于 a = (b = c),幂在多数语言里也是右结合。把优先级和结合性混在一起看是最常见的误解:优先级解决不同层的先后,结合性解决同层的先后,两者都写在表里,缺一不可。至于求值顺序,那是第三件事——括号能改变结合方式,却改变不了函数参数的求值顺序,涉及副作用的表达式要靠拆语句来保证顺序。
下面这几个例子是真实项目里最容易出事的地方,共同点是「看起来像人话,实际绑定的位置和直觉相反」。
a & b == c
C、C++、Java、JavaScript、C#:相等比较比按位与高,表达式实际是 a & (b == c)。因为 == 只给出 0 或 1,结果几乎永远是 0。想比较掩码必须写 (a & b) == c。Go 是唯一相反的语言:它把 & 放进比比较更高的乘法组,同一个式子天然读作 (a & b) == c,不用额外加括号。
1 << 2 + 3
C、C++、Java、JavaScript、PHP、Rust:加法比位移高,所以先算 2 + 3 得 5,再左移 5 位,结果是 32 而不是 8。要让位移先算必须写成 (1 << 2) + 3。C# 也遵循同样的相对顺序,而 Go 和 Python 把位移放进了比加法更高的一层,同一个式子在那两门语言里是 (1 << 2) + 3 等于 7。
not a == b
Python:not 的优先级低于比较运算符,所以它等价于 not (a == b),不是 (not a) == b。这正是「英文运算符读起来像自然语言,但绑定顺序并不按直觉」的典型。
2 ** 3 ** 2
Python、JavaScript、Ruby、PHP:幂运算右结合,等于 2 ** (3 ** 2) = 512,不是 (2 ** 3) ** 2 = 64。同一个符号在 C 家族里根本不存在,在 SQL 里 PostgreSQL 又改成左结合,跨语言搬运公式时要格外小心。
数据的取舍也要交代清楚。这一页只收录各语言官方文档里明确列出的条目,拿不准的宁可不写;SQL 那一栏按 PostgreSQL 的官方文档整理,因为运算符优先级在 SQL 里由各引擎自行定义,MySQL、Oracle、SQL Server 在字符串拼接和整除这几处的行为并不一致,跨库查询请以目标引擎的手册为准。想验证某个表达式到底算出什么,可以把式子丢进科学计算器;整理 SQL 语句用SQL 格式化;确认压缩后的代码有没有因为括号被优化掉优先级,用JS 压缩美化。
常见问题
优先级和结合性有什么区别?
优先级决定「哪些运算先算」,只在不同的运算符之间起作用;结合性解决的是「同一个运算符连着写时从哪头算」,只在优先级相同的运算符之间起作用。a + b * c 由优先级决定先算乘法,而 a - b - c 的两个减号优先级相同,靠左结合决定等价于 (a - b) - c。两栏都要看,只看优先级会漏掉一半。
不同语言的层级数字能直接比吗?
不能。层级序号只在同一门语言内部有意义:这张表把每门语言最高的一层编成 1,但 JavaScript 有 20 层、Go 连一元层算上也只有 6 层,层数多少反映的是分档的粗细——Go 干脆不给运算符重载,所以表很短——不是严格程度。所以页面显示的是「第几层 / 总共几层」。要比的是「某个运算符相对于这门语言里其他运算符的位置」,比如它在加法之上还是之下。
拿不准的时候直接加括号不就行了?
加括号确实是最省事的做法,但它只解决可读性,不改变语义,而且优先级和求值顺序是两回事。a() + b() 里两个函数的调用顺序在 C 和 C++ 里是不确定的,加括号也管不了;f(a, b) 里参数的求值顺序同样没有规定。所以括号能帮你避开优先级坑,但涉及副作用的表达式还得靠拆成多条语句来保证顺序。
a & b == c 为什么会算错?
因为所有从 C 家族继承下来的语言里,相等比较的优先级都高于按位与。表达式实际读作 a & (b == c),而比较的结果只有 0 或 1,于是整个式子几乎永远是 0。想判断掩码必须写 (a & b) == c。Go 是这里唯一相反的语言:它把 & 归进比比较更高的乘法组,同一个式子天然读作 (a & b) == c。
1 << 2 + 3 得到的是 8 还是 32?
是 32。加法的优先级高于位移,所以先算 2 + 3 得 5,再左移 5 位。要让位移先算必须写成 (1 << 2) + 3。这条相对顺序源自 C 的传统,C、C++、Java、JavaScript、PHP、Rust、C# 都沿用;只有 Go 和 Python 把位移放到了比加法更高的一层,同一个式子在那两门语言里等于 7。
Python 的 not a == b 到底是什么意思?
等价于 not (a == b),也就是「a 不等于 b」,而不是 (not a) == b。Python 把 not 放在比较运算符之下,所以英文读起来像自然语言的写法,绑定顺序也符合自然语言。真正反直觉的恰恰是很多人凭直觉以为 not 会先作用于 a。
2 ** 3 ** 2 为什么是 512?
因为幂运算右结合,等于 2 ** (3 ** 2) = 2 ** 9 = 512,而不是 (2 ** 3) ** 2 = 64。Python、JavaScript、Ruby、PHP 的幂都是右结合;PostgreSQL 的 ^ 反而是左结合,同一个数学表达式搬过去结果就变了。幂和一元负号的搭配更麻烦:Python 里幂高于一元负号,所以 -2 ** 2 是 -(2 ** 2) 等于 -4;JavaScript 干脆把 -2 ** 2 定为语法错误,必须写成 -(2 ** 2) 才通过解析。