对于一个复杂的表达式,存在很多的运算符,要了解这个表达式的意义,就要了解清运算符之间的优先关系与结合顺序。要确保表达式实现自己所想达成的目的,了解计算顺序也是非常必要的。

1. 运算符优先级与结合顺序

复杂的表达式可能会有较多的运算符,有时候弄的头大,虽然可以添加括号来使表达式的意义更加明确,但有时候括号太多也会造成混乱,而更不容易理解。掌握C语言中的运算符优先级是非常必要的,也是非常有益的。

下表列出了各类运算符的优先级关系与结合顺序,表中由上至下,优先级依次递减。

运算符                                          结合性
() [] -> .                                    自左向右
! ~ ++ -- + - (type) * & sizeof               自右向左
* / %                                         自左向右
+ -                                           自左向右
<< >>                                         自左向右
< <= > >=                                     自左向右
== !=                                         自左向右
&                                             自左向右
^                                             自左向右
|                                             自左向右
&&                                            自左向右
||                                            自左向右
?:                                            自右向左
assignments                                   自右向左
,                                             自左向右

15级的优先级,看似复杂难记,其实都是蕴含着一定的规律的。

首先,优先级最高的并不是真正意义上的运算符,如数组下标、函数调用操作符和结构成员选择符。它们的结合顺序都是自左向右的。如a.b.c表示(a.b).c,而不是a.(b.c)

单目运算符的优先级仅次于上一种,是真正意义的运算符中优先级最高的。其中的+ -不是加号减号,而是正负号;*是表示指针,不是乘号。现在知道*的优先级是低于[]的,所以int *p[10]中表示p首先是一个数组,然后数组中的元素是一个指向int型的指针,即是一个指针数组。可见了解了运算符的优先级对于掌握复杂声名的分析也是有帮助的。单目运算符是自右向左结合的,所以*p++表示*(p++)而不是(*p)++

接下来是双目运算符。在双目运算符中,算术运算符优先级最高,移位运算符次之,关系运算符再次之,然后是逻辑运算符,条件运算符,最后是各类赋值运算符。其中条件运算符是三目运算符。

需要记住的最重要的两点是:

  1. 任何一个逻辑运算符的优先级低于任何一个关系运算符。

  2. 移位运算符的优先级比算术运算符要低,但高于关系运算符。

算术运算符中乘除、取余优先于加减。关系运算符中注意一下,== !=的优先级低于其他四个关系运算符。

而逻辑运算符中任何两个都有不同的优先级,都是与关系高于或。& |高于&& ||则是因为,前者在B语言中已经出现,而在B的基础上出现的C语言又加入了后者来区分不同的操作,从兼容性的角度,修改其优先顺序很危险。

条件运算符是唯一的三目运算符,优先级也是很低的,仅高于赋值运算符和逗号运算符。赋值运算符次之,这两类运算符的结合顺序是自右向左的。所以

a=b=0;

等同于

b=0;
a=0;

最后则是逗号运算符。被逗号分隔的一对表达式按照自左向右的顺序进行求值,表达式右边的操作数的类型和值即为其结果的类型和值。在for循环中常用逗号操作符,如下:

for(i=0, j = strlen(s)-1; i < j; i++, j--)

某些情况下的逗号并不是运算符,如分隔函数参数的逗号,分隔声明中变量的逗号等。这些逗号并不保证各表达式自左向右的顺序求值。应谨慎用逗号运算符,其最适用与关系紧密的结构中,如for循环、元素交换操作。

有时候表达式写的不规范或马虎,也会造成一些麻烦。如表达式z = y+++x,原本的意图可能是z = y + ++x或是z = y++ + x,但缺少了空格使人迷惑。ANSI C规定了一种逐渐为人所熟知的“maximal munch strategy”。这种策略表示如果下一个标记有超过一种的解决方案,编译器将选择能组成最长字符串序列的方案。所以这个表达式将被解析为后一种意思。但这也会造成一些麻烦,如z = y+++++x将被解析为z = y++ ++ + x就出现了错误。

所以当我们编写代码时,还是要认真一些,遵循一定的编程规范。

2. 计算顺序

讨论完了运算符优先级与结合顺序的问题,计算顺序则完全是另一码事了,不要和结合顺序搞混。C语言没有指定同一运算符中多个操作数的计算顺序(&&、||、?:和逗号运算符除外)。例如

x = f() + g();

的语句中,f()可以在g()之前计算,也可以在其后计算。因此,如果两个函数改变了同一变量,则x的结果将依赖与两个函数的计算顺序。类似的,C语言也没有指定函数各参数的求值顺序。函数调用、嵌套赋值语句、自增、自减运算符都可能产生副作用。

在任何一门编程语言中,如果代码的执行结果与求值顺序相关,则都是不好的程序设计风格。

参考书籍

  • C程序设计语言

  • C专家编程

  • C陷阱与缺陷