重学C语言(二)——运算符优先级与计算顺序
对于一个复杂的表达式,存在很多的运算符,要了解这个表达式的意义,就要了解清运算符之间的优先关系与结合顺序。要确保表达式实现自己所想达成的目的,了解计算顺序也是非常必要的。
1. 运算符优先级与结合顺序
复杂的表达式可能会有较多的运算符,有时候弄的头大,虽然可以添加括号来使表达式的意义更加明确,但有时候括号太多也会造成混乱,而更不容易理解。掌握C语言中的运算符优先级是非常必要的,也是非常有益的。
下表列出了各类运算符的优先级关系与结合顺序,表中由上至下,优先级依次递减。
运算符 结合性
() [] -> . 自左向右
! ~ ++ -- + - (type) * & sizeof 自右向左
* / % 自左向右
+ - 自左向右
<< >> 自左向右
< <= > >= 自左向右
== != 自左向右
& 自左向右
^ 自左向右
| 自左向右
&& 自左向右
|| 自左向右
?: 自右向左
assignments 自右向左
, 自左向右
15级的优先级,看似复杂难记,其实都是蕴含着一定的规律的。
首先,优先级最高的并不是真正意义上的运算符,如数组下标、函数调用操作符和结构成员选择符。它们的结合顺序都是自左向右的。如a.b.c
表示(a.b).c
,而不是a.(b.c)
。
单目运算符的优先级仅次于上一种,是真正意义的运算符中优先级最高的。其中的+ -
不是加号减号,而是正负号;*
是表示指针,不是乘号。现在知道*
的优先级是低于[]
的,所以int *p[10]
中表示p首先是一个数组,然后数组中的元素是一个指向int型的指针,即是一个指针数组。可见了解了运算符的优先级对于掌握复杂声名的分析也是有帮助的。单目运算符是自右向左结合的,所以*p++
表示*(p++)
而不是(*p)++
。
接下来是双目运算符。在双目运算符中,算术运算符优先级最高,移位运算符次之,关系运算符再次之,然后是逻辑运算符,条件运算符,最后是各类赋值运算符。其中条件运算符是三目运算符。
需要记住的最重要的两点是:
任何一个逻辑运算符的优先级低于任何一个关系运算符。
移位运算符的优先级比算术运算符要低,但高于关系运算符。
算术运算符中乘除、取余优先于加减。关系运算符中注意一下,== !=
的优先级低于其他四个关系运算符。
而逻辑运算符中任何两个都有不同的优先级,都是与关系高于或。& |
高于&& ||
则是因为,前者在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陷阱与缺陷