加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

c语言运算符优先级,结合性(左/右结合详解)(zz)

(2014-05-30 13:01:23)
标签:

体育

分类: 编程语言C/C /PERL/Tcl

重新翻了遍c语言,觉得对c语言运算符结合性有必要提一下,毕竟去年我自己刚学c语言的时候,就没搞明白结合性的问题。

什么是左结合?什么是右结合?它们是干嘛用的?它们是什么时候用的?

本文将围绕这几个问题展开..

(小白文,大神绕道)

先附录几个自己学习过程中的参考网站。(有些见解和例子来自一下网站)

http://wenku.baidu.com/link?url=W2ofiBx4yEtrxAAfV8lUaR-bYSIuBox1_DH-1mG6g6q0VeNBR31OfKlaUXRQ6ARQMJEo-r1HVvYBjXVVvtFaSI5CxI4MerOLVxzS3yZRtiS

 http://c.chinaitlab.com/basic/873233.html

 http://bbs.csdn.net/topics/370153775


在讲结合性之前,先提一下c语言的运算符及其优先级关系。


一共有十五个优先级: 

1   ()  []  .  ->

2   !  ~   -(负号) ++  --   &(取变量地址)*   (type)(强制类型)    sizeof 

3   * / %

4   + - 

5   >> <<  

6   > >= < <= 

7   == !=   

8   &  

9   ^   

10  |   

11  &&

12  ||

13  ?:

14   =  +=  -=  *=  /=   %=  |=   ^=   &=   >>=   <<=

15  ,

结合性:2   13  14  是从右至左   其他都是  从左至右有问题可以在交流的 

 

共同进步 

 


括号成员第一;        //括号运算符[]() 成员运算符.  ->

全体单目第二;        //所有的单目运算符比如++、 --、 +(正)、 -(负) 、指针运算*、&乘除余三,加减四;   //这个"余"是指取余运算即%

移位五,关系六;    //移位运算符:<< >> ,关系:> < >= <= 等

等于(与)不等排第七;    //即== 和!=

位与异或和位或;    //这几个都是位运算: 位与(&)异或(^)位或(|)    

"三分天下"八九十;  

逻辑或跟与;            //逻辑运算符:|| 和 &&

十二和十一;            //注意顺序:优先级(||)  底于 优先级(&&) 

条件高于赋值,        //三目运算符优先级排到13 位只比赋值运算符和","高

逗号运算级最低!    //逗号运算符优先级最低 

 

C语言运算符优先级

 

优先级

运算符

名称或含义

使用形式

结合方向

说明

1

[]

数组下标

数组名[常量表达式]

左到右

--

()

圆括号

(表达式)/函数名(形参表)

--

.

成员选择(对象)

对象.成员名

--

->

成员选择(指针)

对象指针->成员名

--

 

2

-

负号运算符

-表达式

右到左

单目运算符

~

按位取反运算符

~表达式

++

自增运算符

++变量名/变量名++

--

自减运算符

--变量名/变量名--

*

取值运算符

*指针变量

&

取地址运算符

&变量名

!

逻辑非运算符

!表达式

(类型)

强制类型转换

(数据类型)表达式

--

sizeof

长度运算符

sizeof(表达式)

--

 

3

/

表达式/表达式

左到右

双目运算符

*

表达式*表达式

%

余数(取模)

整型表达式%整型表达式

4

+

表达式+表达式

左到右

双目运算符

-

表达式-表达式

5

<< 

左移

变量<<表达式

左到右

双目运算符

>> 

右移

变量>>表达式

 

6

大于

表达式>表达式

左到右

双目运算符

>=

大于等于

表达式>=表达式

小于

表达式<表达式

<=

小于等于

表达式<=表达式

7

==

等于

表达式==表达式

左到右

双目运算符

=

不等于

表达式!= 表达式

 

8

&

按位与

表达式&表达式

左到右

双目运算符

9

^

按位异或

表达式^表达式

左到右

双目运算符

10

|

按位或

表达式|表达式

左到右

双目运算符

11

&&

逻辑与

表达式&&表达式

左到右

双目运算符

12

||

逻辑或

表达式||表达式

左到右

双目运算符

 

13

?:

条件运算符

表达式1?

表达式2: 表达式3

右到左

三目运算符

 

14

=

赋值运算符

变量=表达式

右到左

--

/=

除后赋值

变量/=表达式

--

*=

乘后赋值

变量*=表达式

--

%=

取模后赋值

变量%=表达式

--

+=

加后赋值

变量+=表达式

--

-=

减后赋值

变量-=表达式

--

<<=

左移后赋值

变量<<=表达式

--

>>=

右移后赋值

变量>>=表达式

--

&=

按位与后赋值

变量&=表达式

--

^=

按位异或后赋值

变量^=表达式

--

|=

按位或后赋值

变量|=表达式

--

 

15

逗号运算符

表达式,表达式,…

左到右

--

 

说明:

  

    运算符共分为15级,1级优先级最高,15级优先级最低。

    同一优先级的运算符,运算次序由结合方向所决定。(结合性:2 13 14 是从右至左 其他都是 从左至右)
    简单记就是:! > 算术运算符 > 关系运算符 > && > || > 赋值运算符


再详细一点,附带一个口诀。(网上找的)

 

括号成员第一;                //括号运算符[]() 成员运算符. ->

全体单目第二;              //所有的单目运算符比如++、 --、 +(正)、 -(负) 、指针运算*、&
乘除余三,加减四;         //这个"余"是指取余运算即%

移位五,关系六;          //移位运算符:<< >> ,关系:> < >= <= 等

等于(与)不等排第七;      //即== 和!=

位与异或和位或;   "三分天下"八九十;     //这几个都是位运算: 位与(&)异或(^)位或(|) 

逻辑或跟与;              //逻辑运算符:|| 和 &&

十二和十一;           //注意顺序:优先级(||) 底于 优先级(&&) 

条件高于赋值,                //三目运算符优先级排到13 位只比赋值运算符和","高

逗号运算级最低!        //逗号运算符优先级最低 


 

如果想详细了解运算符优先级的关系,我想上面的表格和口诀足以了。

有了以上的基础,我们回归本篇文章的主题---结合性。

观察上述运算符表格我们不难发现,在优先级相同的列表中,有的不止存在一种运算符,那当这两种运算符碰到一起了,该怎么计算呢?

又或者,两个相同的运算符在一起,该怎么进行运算呢?

要解决这些问题,就必须了解结合性了。


语言与其他高级语言相比, 一个显著的特点就是其运算符特别丰富, 共有34 种运算符。C 语言将这34 种运算符规定了不同的优先级别和结合性。优先级是用来标识运算符在表达式中的运算顺序的, 在求解表达式的值的时候, 总是先按运算符的优先次序由高到低进行操作, 可是, 当一个运算对象两侧的运算符优先级别相同时, 则按运算符的结合性来确定表达式的运算顺序。 
运算符的结合性指同一优先级的运算符在表达式中操作的组织方向, 即: 当一个运算对象两侧运算符的优先级别相同时, 运算对象与运算符的结合顺序, 语言规定了各种运算符的结合方向( 结合性) 

大多数运算符结合方向是“自左至右”, 即: 先左后右, 例如a- b+c, 两侧有- 和+两种运算符的优先级相同, 按先左后右结合方向, 先与减号结合, 执行a- 的运算, 再执行加c 的运算。

除了自左至右的结合性外, 语言有三类运算符参与运算的结合方向是从右至左。即: 单目运算符, 条件运算符, 以及赋值运算符。

关于结合性的概念在其他高级语言中是没有的, 这是C语言的特点之一,特别是从右至左结合性容易出错, 下面通过几个具体的运算符来剖析C 语言运算符的结合性。 
若a 是一个变量, 则++a 或a++和- 或a- 分别称为前置加或后置加运算和前置减或后置减运算, 且++a 或a++等价于a=a+1, 或a- 等价于a=a- 1, 即都是使该变量的值增加1 或减少1。由此可知, 对一个变量实行前置或后置运算, 其运算结构是相同的, 但当它们与其他运算结合在一个表达式中时, 其运算值就不同了。

前置运算是变量的值先加1 或减1, 然后将改变后的变量值参与其他运算, 如x=5; y=8; c=++x*y; 运算后, 的值是48,x 的值是6,y 的值是8。

而后置运算是变量的值先参与有关运算, 然后将变量本身的值加1 减1, 即参加运算的是该变量变化前的值。如x=5; y=8; c=x++*y;运算后, 的值是40,x 的值是6, 的值是8。

值得注意的是, 前置、后置运算只能用于变量, 不能用于常量和表达式, 且结合方向是从右至左。如当i=6 时, 求- i++的值和i 的值。由于“- ”(负号) “++”为同一个优先级, 故应理解为- (i++), 又因是后置加, 所以先有- i++的值为- 6, 然后i 增值1 为7, 即i=7。


讲到这里,大家应该都明白了吧。说白了,许多操作符的优先级都是相同的。这时,操作符的结合性就开始发挥作用了。在表达式中如果有几个优先级相同的操作符,结合性就起仲裁的作用,由它决定哪个操作符先执行。先执行,可以看做是加上括号。比如,右结合(从右到左),那就是,把右边的运算用括号先括起来,再与左边的进行计算,这样自然是先执行右边的了。  比如   int a,b=1,c=2;         a=b=c;  这里'='的结合性是从右到左。 故a=b=c; 可变为a=(b=c);  即a=2。


差不多就讲这些了。  

总的来说,掌握上述优先级,结合性是有一定用处的,但也不是必须的。 毕竟,咱们有()这个大招不是,哪里不确定,哪里补()就是了







                                            说说C语言运算符的“优先级”与“结合性”

原文地址:http://blog.csdn.net/steedhorse/article/details/5903974

论坛和博客上常常看到关于C语言中运算符的迷惑,甚至是错误的解读。这样的迷惑或解读大都发生在表达式中存在着较为复杂的副作用时。但从本质上看,仍然是概念理解上的偏差。本文试图通过对三个典型表达式的分析,集中说说运算符的优先级、结合性方面的问题,同时说明它们跟求值过程之间存在的区别与联系。

 

优先级决定表达式中各种不同的运算符起作用的优先次序,而结合性则在相邻的运算符的具有同等优先级时,决定表达式的结合方向。

 

(一)a = b = c;
关于优先级与结合性的经典示例之一就是上面这个“连续赋值”表达式。
b的两边都是赋值运算,优先级自然相同。而赋值表达式具有“向右结合”的特性,这就决定了这个表达式的语义结构是“a = (b = c)”,而非“(a = b) = c”。即首先完成c向b的赋值(类型不同时可能发生提升、截断或强制转换之类的事情),然后将表达式“b = c”的值再赋向a。我们知道,赋值表达式的值就是赋值完成之后左侧操作数拥有的值,在最简单的情况下,即a、b、c的类型完全相同时,它跟“b = c; a = b;”这样分开来写效果完全相同。
一般来讲,对于二元运算符▽来说,如果它是“向左结合”的,那么“x ▽ y ▽ z”将被解读为“(x ▽ y) ▽ z”,反之则被解读为“x ▽ (y ▽ z)”。注意,相邻的两个运算符可以不同,但只要有同等优先级,上面的结论就适用。再比如“a * b / c”将被解读为“(a * b) / c”,而不是“a * (b / c)”——要知道这可能导致完全不同的结果。
而一元运算符的结合性问题一般会简单一些,比如“*++p”只可能被解读为“*(++p)”。三元运算符后面会提到。

 

(二)*p++;
像下面这样实现strcpy函数的示例代码随处都能见到:

 

  1. charstrcpy( chardest, const charsrc ){  
  2.     char*p dest;  
  3.     while(*p++ *src++);  
  4.   
  5.     return dest;  
  6.  


理解这一实现的关键在于理解“*p++”的含义。
首先,解引用运算符“*”的优先级低于后自增运算符“++”,所以,这个表达式在语义上等价于“*(p++)”,而不是“(*p)++”。
论坛上经常有朋友不明白,为什么“p++”加不加括号效果都一样,这就是答案:因为后自增的优先级本来就比解引用高,加上括号也是多余。(这里仅指语义上多余,有人觉得从程序可读性上考虑并不多余,那是另一回事。)
但这里还有一个问题容易让人糊涂,那就是后自增运算符的语义。许多书上都讲“后自增是先取值,后加1。”这么讲当然没错,但在上面这样的while语句中,人们还是容易糊涂。当一个表达式中同时包含自增、解引用和赋值,并最终做为控制循环的条件,所谓的“先取值”又是“先”到什么地步呢?我们还是看看C语言标准上的说法吧。以下摘自C99标准:ISO/IEC 9899:1999:
6.5.2.4-2:The result of the postfix ++ operator is the value of the operand. After the result is obtained, the value of the operand is incremented. …… The side effect of updating the stored value of the operand shall occur between the previous and the next sequence point.
也就是说,后自增表达式的结果值就是被自增之前的那个值,然后这个结果值被确定之后,操作数的值会被自增。而这种“自增”的副作用会在上一个“序列点”跟下一个“序列点”之间完成。
本文不打算详细讨论序列点。有兴趣的读者可以阅读一下标准。需要指出的是:赋值运算在C语言中并不是一个序列点,所以,上面的while语句中,src的自增效果无需是在赋值之前完成。但while的整个控制表达式的结束却是一个序列点。
我们可以这样解析“while(*p++ = *src++) ;”:首先,while当中的条件变量是个赋值表达式,左侧操作数是“*p++”,右侧操作数是“*src++”,整个表达式的值将是赋值完成之后左侧项的值。而左右两侧是对两个后自增表达式解引用。既然解引用作用于整个后自增表达式而不是仅作用于p或src,那么根据上面引用的标准,它们“取用”的分别是指针p和src的当前值。而自增的副作用只需在下一个序列点之前完成即可。
综上所述:编译器要分别取得指针p和src的当前值,基于这个值完成“*src”向“*p”的赋值;同时这个赋值结果成为整个赋值表达式的值,用以决定是否退出while循环。然后,在整个表达式结束时的某一时刻(在不影响之前叙述的前提下),p和src分别被加1。
简言之,整个表达式完全结束之时,我们既完成了基于p和src的旧值所进行的赋值和循环条件判断,也完成了p和src的自增。
显然,这样的描述还是让人头晕。我曾见过关于后自增(后自减)运算的另外两种“说法”,虽然跟C语言标准上的说法并不完全一致,但在最终的语义效果上却如出一辙。这两种说法是:
(1)后自增“x++”相当于一个逗号表达式:“tmp = x, ++x, tmp”;
(2)后自增就是把操作数加1,然后返回加1之前的值作为整个表达式的值。
相对来讲,还是标准中的说法为编译器的实现(特别是优化)留下了更多空间,但上面的这两种“说法”却更便于人的理解,而且跟正确的用法在最终效果上是一致的。在C++语言中,当需要重载后自增运算符时,惯常采用的机制就是基于上面两种说法。

有了这些理解,再来理解类似下面的strlen实现也就没什么问题了:

 

  1. size_t strlen(const charstr){  
  2.     const charstr;  
  3.     while(*p++);  
  4.     return str 1;  
  5.  


注意上面函数中最后的减1。虽然是否退出while循环是由p的当前值解引用决定的,但即使while要退出,在“正式”退出之前,后自增(“++”)加1的副作用还是要体现。也可以这么理解:所谓“退出循环”,是指“不再执行循环体”,但控制表达式并非循环体的一部分,它的所有副作用在整个表达式结束之前都会生效。所以,我们最后要减掉循环退出时多走的这一步。
还想重复一遍:*p++就是*(p++),它们除了可读性之外没有任何区别,所以那种认为加上括号就可以实现先加1再解引用的想法是错误的。要达到那样的效果,可以用“*++p”。

 

(三)x > y ? 100 : ++y > 2 ? 20 : 30
这个表达式看起来有点吓人。让我们先给出更多的上下文吧:

  1. int 3;  
  2. int 2;  
  3. int 100 ++y 20 30;  


此时,z的值该是多少呢?
这里面是两个条件运算符(?:,也叫“三目运算符”)嵌套,许多人会去查条件运算符的特性,得知它是“向右结合”的,于是认为右侧的内层条件运算“++y > 2 ? 20 : 30”先求值,这样y首先被加1,大于2的条件成立,从而使第二个条件运算取得结果“20”;然后再来求值整个条件表达式。这时,由于y已经变成3,“x > y”不再成立。整个结果自然就是刚刚求得的20了。
这种思路是错误的。
错误的原因在于:它把优先级、结合性跟求值次序完全混为一谈了。
首先,在多数情况下,C语言对表达式中各子表达式的求值次序并没有严格规定;其次,即使是求值次序确定的场合,也是要先确定了表达式的语义结构,在获得确定的语义之后才谈得上“求值次序”。
对于上面的例子,条件运算符“向右结合”这一特性,并没有决定内层的条件表达式先被求值,而是决定了上面表达式的语义结构等价于“x > y ? 100 : (++y > 2 ? 20 : 30)”,而不是等价于“(x > y ? 100 : ++y) > 2 ? 20 : 30”。——这才是“向右结合”的真正含义。
编译器确定了表达式的结构之后,就可以准确地为它产生运行时的行为了。条件运算符是C语言中为数不多的对求值次序有明确规定的运算符之一(另位还有三位,分别是逻辑与“&&”、逻辑或“||”和逗号运算符“,”)。
C语言规定:条件表达式首先对条件部分求值,若条件部分为真,则对问号之后冒号之前的部分求值,并将求得的结果作为整个表达式的结果值,否则对冒号之后的部分求值并作为结果值。
因此,对于表达式“x > y ? 100 : (++y > 2 ? 20 : 30)”,首先看x大于y是否成立,在本例中它是成立的,因此整个表达式的值即为100。也因此冒号之后的部分得不到求值机会,它的所有副作用也就没机会生效。

 

总结一下,本文主要阐述了以下几点:
(1)优先级决定表达式中各种不同的运算符起作用的优先次序,而结合性则在相邻的两个运算符的具有同等优先级时,决定表达式的结合方向;
(2)后自增(后自减)从语义效果上可以理解为在做完自增(自减)之后,返回自增(自减)之前的值作为整个表达式的结果值;
(3)准确来讲,优先级和结合性确定了表达式的语义结构,不能跟求值次序混为一谈。

 

[PS-1] 维基百科上有C/C++语言运算符表:http://en.wikipedia.org/wiki/Operators_in_C_and_C++
[PS-2] 曾在新浪微博上见benbearchen提到有的公司在代码规范中要求:如果while的循环体为空语句,那么必需以continue语句代替,不准只写一个分号。我本人很赞成这个。上面strcpy和strlen的两个例子之所以没那么用,只是为了“随大流”,因为这两个函数的示例实现,许多人、许多书上都这么写。




例子:

for (i = 0; i < (a < 0) ? 3 : 4; i ++)

{

   .......


}

在这个例子中,因为比较运算符"<" 的优先级高于 "?" ,因此上面会等价于:

for (i = 0; (i < (a<0)) ? 3: 4; i ++) {

}

这和本来的意义是不一样的,因此需要用括号:

for (i = 0; i < ((a < 0) ? 3 : 4); i ++)

{

   .......


}

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有