`
xpp02
  • 浏览: 1012270 次
社区版块
存档分类
最新评论

运算符的优先级总能起作用吗?

 
阅读更多
有关运算符优先级的规则稍微有点复杂。在大多数情况下,这些规则确实是你所需要的,然而,有人也指出其中的一些规则本来是可以设计得更好的。

让我们快速地回顾一些有关内容:“运算符优先级”是这样一些规则的集合——这些规则规定了“运算符”(例如+,-,等等)的优先性,即哪一种运算符先参加运算。在数学中,表达式“2×3+4×5”和“(2×3)+(4×5)”是等价的,因为乘法运算在加法运算之前进行,也就是说乘法的优先级比加法高。

在c中,有16级以上的运算符优先级。尽管这么多的规则有时使c程序不易阅读,但也使C程序写起来容易多了。虽然这不是唯一的一种折衷方法,但这就是C所采用的方法。表16.1总结了运算符的优先级。
表16.1 运算符优先级总结(从高到低)
----------------------------------------------------------------------------------
优先级 运算符
----------------------------------------------------------------------------------
1 x[y](下标)
x(y)(函数调用)
x.y(访问成员)
x->y(访问成员指针)
x++(后缀自增)
x--(后缀自减)--
2 ++x(自增)
--x(自减)
&x(取地址)
*x(指针引用)
+x(同x,和数学中相同)
-x(数学求负)
!x(逻辑非)
~x(按位求反)
sizeof x和sizeof(x_t)(字节数大小)
3(x_t)y(强制类型转换)
4x*y(乘法)
x/y(除法)
x%y(求余)
5 x+y(加法)
x-y(减法)
6 x<<y(按位左移)
x>>y(按位右移)
7 x<y,x>y,x<=y,x>=y(关系比较)
8 x==y,x!=y(相等比较)
9 x&y(按位与)
10 x^y(按位异或) .
11 x | y(按位或)
12 x&&y(逻辑与)
13 x||y(逻辑或)
14 x?y:z(条件)
x=y,x*=y,x/=y,x+=y,x-=y,<<=,>>=,&=,^=,|=(赋值,右结合性)
16 x,y(逗号)
--------------------------------------------------------------------------------------

优先级最高的是后缀表达式,即运算符跟在一个表达式后面;其次是前缀或单目表达式,即运算符位于一个表达式的前面;再次是强制类型转换表达式。

注意:关于运算符优先级,最重要的是知道*p++和*(p++)是等价的。也就是说,在*p++中,++运算符作用在指针上,而不是作用在指针所指向的对象上。象“*p++=*q++;这样的代码在C中是随处可见的,其中的优先级和“(*(p++))=(*(q++))”中的是相同的。这个表达式的含义是“q+1,但仍用q原来的值找到q所指向的对象;p加1,但仍用p原来的值;把q所指向的对象赋给p所指向的对象”,整个表达式的值就是原来q所指向的对象。在C中你会经常看到这样的代码,并且你会有许多机会去写这样的代码。对于其它运算符,如果你记不住其优先级,可以查阅有关资料,但是,一个好的c程序员应该连想都不用想就能明白*p++的含义。

最初的C编译程序是为这样一种计算机编写的——它的某些指令对象*p++和*p++=*q++这样的代码的处理效率高得令人难以置信,因此,很多C代码就写成这种形式了。进一步地,因为象这样的C代码实在太多了,所以新机型的设计者会保证提供能非常高效地处理这些C代码的指令。

再下一级的优先级是乘法、除法和求余(也叫取模),再往后是加法和减法。与数学中的表达式相同,“2*3+4*5”和“(2*3)+(4*5)”是等价的。

再下一级是移位运算。

再往后两级分别是关系比较(例如x<y)和相等比较(x==y和x!=y)。

再往后三级分别是按位与、按位异或和按位或。

注意:关于运算符优先级,再次重要(即在知道*p++和x=y=z的含义之后)的是要知道x&y==z和(x&y)==z是不一样的。因为按位操作的运算符的优先级低于比较运算符,所以x&y==z和x&(y==z)是等价的。这两个表达式的含义都是“先看y和z是否相等(相等为1,不等为0),然后让比较结果和x进行按位与运算”,这与“先让x和y进行按位与运算,再比较其结果是否等于z”相差甚远。有人可能会争辩,按位与运算符的优先级应该高于比较运算符,但为时已晚,因为相应的标准是早在二十年前被定义的。如果你想把按位与的结果与别的东西进行比较,你就需要使用括号。

再往后两级是逻辑运算符,例如x&&y和x||y。注意,逻辑与(AND)运算符的优先级高于逻辑或(OR)运算符,这与人们讲话的方式是一致的。例如,请看下面的代码:
if(have_ticket&&have_reservation
||have_money && standby_ok){
goto_airport();
}

这段代码的含义可以这样来描述:“如果你有机票并且预定了航班,或者你有钱并且可以买到备用票,那么你就可以出发去机场了。”如果你用括号改变优先级,你就会得到一种截然不同的条件:
/* not a recommended algorithm!*/
if(have_ticket
&&(have_reservation || have_money)
&&standby_ok){
goto airport ();
}

这段代码的含义可以这样来描述:“如果你有机票,并且你预定好了航班或者有钱,并且可以买到备用票,那么你就可以出发去机场了。”

再下一级是条件表达式,例如x?y:z。这是一个if-then-else结构的表达式,而不是一条语句。条件表达式有时可以使程序简洁,有时也会造成语意的模糊。条件表达式具有右结合性,也就是说
a?b:c?d:e
等价于
a?b:(c?d:e)
这一点与else—if结构很相似。

再下一级是赋值运算。所有的赋值运算符都具有相同的优先级。与C的其它双目运算符不同,赋值运算具有“右结合性”,即它是从右向左进行的,而不是从左向右进行的。x+y+z等价于(x+y)+z,x*y+z等价于(x*y)+z,而x=y=z等价于x=(y=z)。

注意:关于运算符优先级,次重要(即在知道*p++的含义之后)的是要知道x=y=z的含义。因为赋值运算具有右结合性,所以这个表达式等价于x=(y=z),其含义是“将z的值赋给y,然后再将该值赋给x”。象a=b=c=d=O;

这样的代码是很常见的,按从右向左的顺序,它把。赋给d,再赋给c,再赋给b,最后赋给a。

c中优先级最低的是逗号运算符。它连接两个表达式,先计算第一个表达式的值,扔掉后,再计算第二个表达式的值。只有当第一个表达式具有副作用时,例如赋值或函数调用,使用逗号运算符才有意义。逗号和赋值运算符经常在for循环语句中搭配使用:
for(i=0,count=O;i<MAX;++i){
if(interestmg(a[i])){
++count:
}
}

高位优先(big—endian)与低位优先(little-endian)的计算机有什么区别?

高位优先与低位优先的区别仅仅在于一个字的哪一端是高位字节。换句话说,两者的区别在于你是喜欢从左向右数,还是喜欢从右向左数。但是,哪种方式都不见得比另一种方式更好。一个可移植的C程序必须能同时适用于这两种类型的计算机。

假设你的程序运行在short类型为两字节长的计算机上,并且把值258(十进制)存放到地址s3000H处的一个short类型中。因为short类型的长度为两字节,所以该值的一个字节存放在3000H处,另一个字节存放在3001H处。258(十进制)即0102H,所以该值的一个字节的内容为1,另一个字节的内容为2。那么,究竟内容为1和2的字节分别是哪一个呢?

其答案因机器的不同而不同。在高位优先的计算机上,高位字节就是低地址字节(“高位字节”指的是其值变化后使整个字的值变化最大的那个字节,例如,在值0102H中,01H就是高位字节,而02H是低位字节)。在高位优先的计算机上,字节中的内容如下所示:
地址 2FFEH 2FFFH 3000H 3001H 3002H 3003H
值 01H 02H

这种图示方式很直观——地址就象是尺子上的刻度值,低地址在左,高地址在右。在低位优先的计算机上,字节中的内容如下所示:
地址 3003H 3002H 3001H 3000H 2FFFH 2FFEH
值 01H 02H
这种图示方式同样很直观——低位字节存放在低地址中。

不幸的是,有些计算机采用高位优先的存储方式,而另一些计算机却采用低位优先的存储方式。例如,IBM兼容机和Macintosh机对高位字节和低位字节的处理方法就不同。

为什么这种区别会产生影响呢?试想一下,如果用fwrite()直接把一个short类型的值按两字节存到文件或网络上,不考虑格式和是否可读,而只是存为紧凑的二进制形式,会引起什么后果呢?如果在高位优先的计算机上存入这个值,而在低位优先的计算机上读出该值(或者反过来),那么存入的是0102H(258),读出的就是0201H(513)。

解决这个问题的办法是选择一种存储(和读取)方式,并且自始至终使用这种方式,而不是按存入内存的方式来存储short或int类型的值。例如,有些标准指定了“网络字节顺序(network byte order)”,它是一种高位优先顺序(即高位字节存放在低地址中)。例如,如果s是一个short类型值而a是一个由两个char类型组成的数组,那么下面这段代码
a[0]=(s>>4)& Oxf;
a[1]=s&0xf;
将把s的值按网络字节顺序存入a的两个字节中。不管程序是运行在高位优先或低位优先的计算机上,s的值都会存成这种形式。

你可能会注意到,笔者一直没有提到哪种计算机是高位优先或低位优先的计算机。这样做是有目的的——如果可移植性是重要的,你就应该按这两种类型的计算机都能接受的方式编写程序;如果效率是重要的,通常你仍然要按这两种类型的计算机都能接受的方式编写程序。

例如,在高位优先的计算机上可以用一种更好的方法去实现上例中的那段代码,即使你使用了上例中的代码,一个好的编译程序仍然会利用那种更好的实现来产生机器代码。

注意:“big-endian"和"little-endian"这两个名称来源于Jonathan Swift所写的《格列佛游记>>(Gulliver's Travels)一书。在格列佛第三次出海时,他遇到了这样一群人,他们对煮熟了的鸡蛋的吃法争论不休:有的要先吃大头,有的要先吃小头。

“网络字节顺序”只适用于int,short和long类型。char类型的值按定义只有一字节长,因此字节顺序与它无关。对于float和double类型的值,没有一种标准的存储方式。

C语言运算符的优先级总能起作用吗(从左至右,从右至左)?

如果你是指“一个运算符的结合性会从自右至左变为自左至右吗?反过来会吗?”,那么答案是否定的。如果你是指“一个优先级较低的运算符会先于一个优先级较高的运算符被执行吗?”,那么答案是肯定的。表14.9按优先级从高到低的顺序列出了所有的运算符及其结合性:
表14.9运算符优先级
----------------------------------------------------------------
运算符 结合性
----------------------------------------------------------------
() [] -> 自左至右
! ~ ++ -- -(类型转换) * & 自右至左
sizeof * / % 自左至右
+ - 自左至右
<< >> 自左至右
<< = >>= 自左至右
== !=自左至右
& 自左至右
^ 自左至右
| 自左至右
&& 自左至右
|| 自左至右
?: 自右至左
= += -= 自右至左
, 自左至右
------------------------------------------------------------------
注意,运算符“!=”的优先级高于“=”(实际上,几乎所有的运算符的优先级都高于“=”)。下面两行语句说明了运算符优先级的差异是怎样给程序员带来麻烦的:
while(ch=getch()!=27)printf(”Got a character\n”);
while((ch=geteh())!=27)printf("Got a character\n"); ’

显然,上述语句的目的是从键盘上接收一个字符,并与十进制值27(Escape键)进行比较。不幸的是,在第一条语句中,getch()与Escape键进行了比较,其比较结果(TRUE或FALSE)而不是从键盘上输入的字符被赋给了ch。这是因为运算符“!=”的优先级高于“=”。

在第二条语句中,表达式"ch=geteh()”的外边加上了括号。因为括号的优先级最高,所以来自键盘的字符先被赋给ch,然后再与Escape键进行比较,并把比较结果(TRUE或FALSE)返回给while语句,这才是程序真正的目的(当while的条件为TRUE时,打印相应的句子)。需要进一步提出的是,与27比较的并不是ch,而是表达式"ch—getch()”的结果。在这个例子中,这一点可能不会造成什么影响,但括号确实可以改变代码的组织方式和运行方式。当一个语句中有多个用括号括起来的表达式时,代码的执行顺序是从最里层的括号到最外层,同层的括号则从左到右执行。

注意,每个运算符在单独情况下的结合性(自左至右,或自右至左)都是不会改变的,但优先级的顺序可以改变。

C语言取模运算符(modulus operator)“%”的作用是什么

取模运算符“%”的作用是求两个数相除的余数。例如,请看下面这段代码:
x=15/7;
如果x是一个整数,x的值将为2。然而,如果用取模运算符代替除法运算符"/",得到的结果就不同了:
X=15%7;
这个表达式的结果为15除以7的余数,等于1。这就是说,15除以7得2余1。

取模运算符通常用来判断一个数是否被另一个数整除。例如,如果你要打印字母表中序号为3的倍数的字母,你可以使用下面这段代码:
int x;
for(x=1; x<=26; x++)
if((x%3)==0)
printf("%c"; x+64);
上例将输出字符串"cfilorux",即字母表中序号为3的倍数的所有字母。

++var和var++有什么区别(C语言自增自减详解)

“++”运算符被称为自增运算符。如果“++”运算符出现在变量的前面(++var),那么在表达式使用变量之前,变量的值将增加1。如果“++”运算符出现在变量之后(var++),那么先对表达式求值,然后变量的值才增加1。对自减运算符(--)来说,情况完全相同。如果运算符出现在变量的前面,则相应的运算被称为前缀运算;反之,则称为后缀运算。

例如,请看一个使用后缀自增运算符的例子:
int x, y;
x=1;
y=(x++* 5);
上例使用了后缀自增运算符,在求得表达式的值之后,x的值才增加1,因此,y的值为1乘以5,等于5。在求得表达式的值之后,x自增为2。

现在看一个使用前缀自增运算符的例子:
int x, y;
x=1;
y=(++x*5);
这个例子和前一个相同,只不过使用了前缀自增运算符,而不是后缀自增运算符,因此,x的值先增加1,变为2,然后才求得表达式的值。这样,y的值为2乘以5,等于10。

C语言运算符的优先级总能保证是“自左至右”或“自右至左”的顺序吗

对这个问题的简单回答是:这两种顺序都无法保证。C语言并不总是自左至右或自右至左求值,一般说来,它首先求函数值,其次求复杂表达式的值,最后求简单表达式的值。

此外,为了进一步优化代码,目前流行的大多数C编译程序常常会改变表达式的求值顺序。因此,你应该用括号明确地指定运算符的优先级。例如,请看下述表达式:
a=b+c/d/function—call() * 5
上述表达式的求值顺序非常模糊,你很可能得不到所要的结果,因此,你最好明确地指定运算符的优先级:
a=b+(((c/d)/function—call())* 5)
这样,就能确保表达式被正确求值,而且编译程序不会为了优化代码而重新安排运算符的优先级了。

C语言右值(rvaule)是什么

在1.9中,左值被定义为可被赋值的表达式,你也可以认为左值是出现在赋值语句左边的表达式。这样,右值就可以被定义为能赋值的表达式,它出现在赋值语句的右边。与左值不同,右值可以是常量或表达式:例如:
int X,y;
x = 1; /* 1 iS an rvalue, x is an lvalue */
y=(x+1); /* (x+1)is an rvalue;y is an lvalue */

在前面已经介绍过,一条赋值语句必须有一个左值和一个右值,因此,下述语句无法通过编译,因为它缺少一个右值:
int x;
x=void_function_call(); /* the{unction void—function—call()
returns nothing */
如果上例中的函数返回一个整数,那么它可以被看作一个右值,因为它的返回值可以存储到左值x中。

请参见:
1、什么是左值(lvaule)?
2、数组可以是左值吗?

C语言数组(array)可以是左值吗

在1.9中,左值被定义为可被赋值的表达式。那么,数组是可被赋值的表达式吗?不是,因为数组是由若干独立的数组元素组成的,这些元素不能作为一个整体被赋值。下述语句是非法的:
int x[5],y[5];
x=y;
不过,你可以通过for循环来遍历数组中的每个元素,并分别对它们赋值,例如:
int i;
int x[5];
int y[5];
......
for(i=0; i<5,i++)
x[i]=y[i];
......

此外,你可能想一次拷贝整个数组,这可以通过象memcpy()这样的函数来实现,例如:
memcpy(x,y,sizeof(y));
与数组不同,结构(structure)可以作为左值。你可以把一个结构变量赋给另一个同类型的结构变量,例如:
typedef struct t_name
{
charlast_name[25];
char first_name[15];
char middle-init [2];
} NAME
...
NAME my_name, your_name;
...
your_name = my_name;
...
在上例中,结构变量my_name的全部内容被拷贝到结构变量your_name中,其作用和下述语句是相同的:
memcpy(your_name,my_name,sizeof(your_name);

请参见:
1、什么是左值(lvaule)?
2、什么是右值(rvaule)?

什么是C语言左值(lvaule)

左值是指可以被赋值的表达式。左值位于赋值语句的左侧,与其相对的右值(rvaule,见 1.11)则位于赋值语句的右侧。每条赋值语句都必须有一个左值和一个右值。左值必须是内存中一个可存储的变量,而不能是一个常量。下面给出了一些左值的例子:

int x;
int *p_int;
x=1;
p_int=5;

变量x是一个整数,它对应于内存中的一个可存储位置,因此,在语句“x=1”中,x就是一个左值。注意,在第二个赋值语句“*p_int=5"中,通过“*”修饰符访问p_int所指向的内存区域;因此,p_int是一个左值。

相反,下面的几个例子就不是左值:
#define CONST_VAL 10
int x
/* example 1 * /
l=x;
/ * example 2 * /
CONST_VAL = 5;

在上述两条语句中,语句的左侧都是一个常量,其值不能改变,因为常量不表示内存中可
存储的位置。因此,这两条赋值语句中没有左值,编译程序会指出它们是错误的。

请参见:
1、 数组(array)可以是左值吗? .
2、 什么是右值(rvaule)?
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics