您所在的位置: 首页>>读书频道>>设计开发>>其它开发>>

2.2 运算符的优先级问题

http://book.51cto.com  2008-04-18 11:24  Andrew Koenig  人民邮电出版社  我要评论(0)

2.2  运算符的优先级问题

假设存在一个已定义的常量FLAG,FLAG是一个整数,且该整数值的二进制表示中只有某一位是1,其余各位均为0,亦即该整数是2的某次幂。如果对于整型变量flags,我们需要判断它在常量FLAG为1的那一位上是否同样也为1,通常可以这样写:

if (flags & FLAG) …

上式的含义对大多数C程序员来说是显而易见的:if语句判断括号内表达式的值是否为0。考虑到可读性,如果对表达式的值是否为0的判断能够显式地加以说明,无疑使得代码自身就起到了注释该段代码意图的作用。其写法如下,

 if (flags & FLAG != 0) …
这个语句现在虽然更好懂了,但却是一个错误的语句。因为!=运算符的优先级要高于 & 运算符,所以上式实际上被解释为:
if (flags & (FLAG != 0) ) …

因此,除了FLAG恰好为1的情形,FLAG为其他数时这个式子都是错误的。

又假设hi和low是两个整数,它们的值介于0到15之间,如果r是一个8位整数,且r的低4位与low各位上的数一致,而r的高4位与hi各位上的数一致。很自然会想到要这样写:

 r = hi<<4 + low;
但是很不幸,这样写是错误的。加法运算的优先级要比移位运算的优先级高,因此本例实际上相当于:
r = hi<< (4 + low);
对于这种情况,有两种更正方法:第一种方法是加括号;第二种方法意识到问题出在程序员混淆了算术运算与逻辑运算,但这种方法牵涉到的移位运算与逻辑运算的相对优先级就更加不是那么明显。两种方法如下:
r = (hi<<4) + low;     //法1:加括号
r = hi<<4 | low;        //法2:将原来的加号改为按位逻辑或

用添加括号的方法虽然可以完全避免这类问题,但是表达式中有了太多的括号反而不容易理解。因此,记住C语言中运算符的优先级是有益的。
遗憾的是,运算符优先级有15个之多,因此记住它们并不是一件容易的事。完整的C语言运算符优先级表如表2-1所示。
表2-1  C语言运算符优先级表(由上至下,优先级依次递减)

()  []  ->  .

自左向右

!  ~  ++  --  -  (type)  *  &  sizeof

自右至左

*  /  %

自左向右

+  -

自左向右

<<  >>

自左向右

<  <=  >  >=

自左向右

==  !=

自左向右

&

自左向右

^

自左向右

|

自左向右

&&

自左向右

||

自左向右

?:

自右至左

assignments

自右至左

,

自左向右


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

优先级最高者其实并不是真正意义上的运算符,包括:数组下标、函数调用操作符各结构成员选择操作符。它们都是自左于右结合,因此a.b.c的含义是(a.b).c,而不是a.(b.c)。

单目运算符的优先级仅次于前述运算符。在所有的真正意义上的运算符中,它们的优先级最高。因为函数调用的优先级要高于单目运算符的优先级,所以如果p是一个函数指针,要调用p所指向的函数,必须这样写:(*p)()。如果写成*p(),编译器会解释成*(p())。类型转换也是单目运算符,它的优先级和其他单目运算符的优先级一样。单目运算符是自右至左结合,因此*p++会被编译器解释成*(p++),即取指针p所指向的对象,然后将p递增1;而不是(*p)++,即取指针p所指向的对象,然后将该对象递增1。本书3.7节还进一步指出了p++的含义有时会出人意料。

优先级比单目运算符要低的,接下来就是双目运算符。在双目运算符中,算术运算符的优先级最高,移位运算符次之,关系运算符再次之,接着是逻辑运算符,赋值运算符,最后是条件运算符。

译注:原书如此,条件运算符实际应为三目运算符。

我们需要记住的最重要的两点是:
1.任何一个逻辑运算符的优先级低于任何一个关系运算符。
2.移位运算符的优先级比算术运算符要低,但是比关系运算符要高。

属于同一类型的各个运算符之间的相对优先级,理解起来一般没有什么困难。乘法、除法和求余优先级相同,加法、减法的优先级相同,两个移位运算符的优先级也相同。1/2*a的含义是(1/2)*a,而不是1/(2*a),这一点也许会让某些人吃惊,其实在这方面C语言与Fortran语言、Pascal语言以及其他程序设计语言之间的行为表现并无差别。

但是,6个关系运算符的优先级并不相同,这一点或许让人感到有些吃惊。运算符==和!=的优先级要低于其他关系运算符的优先级。因此,如果我们要比较a与b的相对大小顺序是否和c与d的相对大小顺序一样,就可以这样写:

a < b == c < d

任何两个逻辑运算符都具有不同的优先级。所有的按位运算符优先级要比顺序运算符的优先级高,每个“与”运算符要比相应的“或”运算符优先级高,而按位异或运算符(^运算符)的优先级介于按位与运算符和按位或运算符之间。

这些运算符的优先顺序是由于历史原因形成的。B语言是C语言的“祖先”,B语言中的逻辑运算符大致相当于C语言中的&和 | 运算符。虽然这些运算符从定义上而言是按位操作的,但是当它们出现在条件语句的上下文中时,B语言的编译器会将它们作为相当于现在C语言中的&&和 || 运算符处理。而到了C语言中,这两种不同的用法被区分开来,从兼容性的角度来考虑,如果对它们优先顺序的改变过大将是一件危险的事。

在本节到现在为止提及的所有运算符中,三目条件运算符优先级最低。这就允许我们在三目条件运算符的条件表达式中包括关系运算符的逻辑组合,例如:

 tax_rate = income>40000 && residency<5 ? 3.5: 2.0;

本例其实还揭示了:赋值运算符的优先级低于条件运算符的优先级是有意义的。此外,所有的赋值运算符的优先级是一样的,而且它们的结合方式是从右到左,因此,

home_score = visitor_score = 0;

与下面两条语句所表达的意思是相同的:

 visitor_score = 0;
home_score = visitor_score;

在所有的运算符中,逗号运算符的优先级最低。这一点很容易记住,因为逗号运算符常用于在需要一个表达式而不是一条语句的情形下替换作为语句结束标志的分号。逗号运算符在宏定义中特别有用,这一点在本书的6.3节还会进一步讨论。
在涉及到赋值运算符时,经常会引起优先级的混淆。考虑下面的这个例子,例子中循环语句的本意是复制一个文件到另一个文件:

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

在while语句的表达式中,c似乎是首先被赋予函数getc(in)的返回值,然后与EOF比较是否到达文件结尾以便决定是否终止循环。然而,由于赋值运算符的优先级要低于任何一个比较运算符,因此c的值实际上是函数getc(in)的返回值与EOF比较的结果。此处函数getc(in)的返回值只是一个临时变量,在与EOF比较后就被“丢弃”了。因此,最后得到的文件“副本”中只包括了一组二进制值为1的字节流。

上例实际应该写成:

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

如果表达式再复杂一点,这类错误就很难被察觉。例如,本书第4章章首提及的lint程序的一个版本,在发布时包括了下面一行错误代码:

 if( (t=BTYPE(pt1->aty)==STRTY) || t==UNIONTY){
这行代码本意是首先赋值给t,然后判断t是否等于STRTY或者UNIONTY。实际的结果却大相径庭:根据BTYPE(pt1->aty)的值是否等于STRTY,t的取值或者为1或者为0;如果t取值为0,还将进一步与UNIONTY比较。
【责任编辑:夏书 TEL:(010)68476606】

回书目   上一节   下一节
程序员如何成长?
深入Vista应用程序开发
Ruby on Rails 社区网站开发
ASP.NET从入门到精通
Java完全自学宝典
 
 验证码: (点击刷新验证码)   匿名发表
  • Visual C++ 完全自学宝典

  • 作者:强锋科技,朱洪波
  • Visual C++ 6.0是微软公司为程序人员提供的Visual Studio 6.0工具套件中的重要组成部分。本书由浅入深地介绍使用Visual C++ 6.0..
Copyright©2005-2008 51CTO.COM 版权所有