如何记住 C 语言运算符优先级表?

在开发中,我们很少关注运算符优先级的问题,因为我们可以滥用 括号,而且我觉得用括号可以厘清逻辑思路,也方便其他人阅读代码。常见的几种与优先级有关的代码如下:

1
2
3
if (a && b != 0) { } // A
if (flags & FLAG != 0) { } // B
r = high<<4 + low; // C

A 是比较常见的写法,先进行 != 运算,再做 && 运算。但我一般会写成 (a && (b != 0))

写 B 的本意是先计算 flag & FLAG,再将结果与 0 比较。然而,由于 != 优先级高于&,上述式子会被解释为 (flag & (FLAG != 0)),违背了本意。

因为移位运算符的优先级比加法运算符低,C 中的式子实际被解释为 r = high << (4 + low);,会得到意想不到的结果。

用添加括号的方式虽然可以完全避免这类问题,但是表达式中有了太多的括号反而不容易理解(上面这种不算多)。因此,记住 C 语言中运算符的优先级是有益的。

然而,运算符优先级有 15 个,因此记住它们并不是一件容易的事。下面先看一下运算符优先级表:

技巧:记符号类型,不记符号。

前述运算符好像也叫做后缀运算符(postfix operators)。

符号类型 运算符 结合性
前述(Preceding)运算符 () [] -> . 左 → 右
单目运算符 ! ~ ++ -- - (type) * & sizeof 右 → 左
双目运算符(算术运算符) * / % 左 → 右
| + - 左 → 右
(移位运算符) << >> 左 → 右
(关系运算符) < <= > >= 左 → 右
| == != 左 → 右
(逻辑运算符) & 左 → 右
| ^ 左 → 右
| | 左 → 右
(条件运算符) && 左 → 右
| || 左 → 右
三目运算符 ? : 右 → 左
赋值运算符 = 右 → 左
逗号运算符 , 左 → 右

如果把这些运算符适当分组,并且理解各组运算符之间的相对优先级,那么就不难记住这张表了。

  • 优先级最高的前述运算符其实并不是真正意义上的运算符(数组下标、函数调用操作符、各结构体成员选择操作符)。

  • 单目运算符的优先级仅次于前述运算符。在所有真正意义上的运算符上,它们的优先级最高。因为函数调用的优先级要高于单目运算符的优先级,所以如果 p 是一个函数指针,要调用所指向的函数需要这么写:(*p)(),即给 *p 加括号,否则编译器会解释为:*(p());类型转换 (type) 也是单目运算符。

  • 单目运算符的结合性是 右 → 左,因此 *p++ 会被编译器解释为 *(p++),即取指针 p 所指向的内容,然后将 p 本身递增 1;而 (*p)++ 是先取指针 p 所指向的对象,然后将该对象递增 1。

  • 双目运算符的优先级:

    • 算术运算符:乘除、加减
    • 移位运算符:<<、>>
    • 关系运算符:大于小于先,等于不等于后
    • 逻辑运算符:位运算 &、|、^
    • 条件运算符:&&、||(条件运算符实际应为三目运算符)

    关键是:

      1. 任意一个逻辑运算符的优先级低于任何一个关系运算符。
      2. 移位运算符的优先级比算术运算符要低,但是比关系运算符要高。
      3. 6 个关系运算符的优先级并不相同,`==`和`!=`的优先级低于其他关系运算符的优先级。因此,对于这样的代码我们可以不用加括号:`a < b == c < d`。
    
  • 因为三目条件运算符优先级最低,这允许我们在三木条件运算符的条件表达式中包括关系运算符的逻辑组合,如:

    1
    tax_rate = income > 4000 && residency < 5 ? 3.5 : 2.0;
  • 赋值运算符经常引起优先级的混淆,如下面这个例子:

    1
    2
    while (c = getc(in) != EOF)
    putc(c, out);

    在 while 语句的表达式中,c 似乎是首先被赋予函数 getc(in) 的返回值,然后再与 EOF 比较是否到达文件结尾以便决定是否终止循环。然而,由于赋值运算符的优先级要低于任何一个比较运算符(关系运算符、条件运算符),因此 c 的值实际上是函数 getc(in) 的返回值与 EOF 比较的结果。此处函数 getc(in) 的返回值只是一个临时变量,在与 EOF 比较后就被「丢弃」了。实际应该写成:

    1
    2
    while ((c = getc(in)) != EOF)
    putc(c, out);
  • 在所有的运算符中,逗号运算符的优先级最低。

最后,我再总结一下优先级顺序:

  • 前述
  • 单目
  • 双目
    • 算术
    • 移位
    • 关系(大于小于在前,等于不等于在后)
    • 逻辑(与、异或、或)
    • 条件
  • 三目
  • 赋值
  • 逗号

《C 专家编程》:有些专家建议在 C 语言中记牢两个优先级就够了(乘法和除法先于加法和减法),在涉及其他的操作符时一律加上括号。「我」认为这是条很好的建议。

参考:《C 陷阱与缺陷,C Traps and Pitfalls》P19(运算符的优先级问题)