说说C语言运算符的“优先级”与“结合性”
2010-09-24 23:58
281 查看
论坛和博客上常常看到关于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。虽然是否退出while循环是由p的当前值解引用决定的,但即使while要退出,在“正式”退出之前,后自增(“++”)加1的副作用还是要体现。也可以这么理解:所谓“退出循环”,是指“不再执行循环体”,但控制表达式并非循环体的一部分,它的所有副作用在整个表达式结束之前都会生效。所以,我们最后要减掉循环退出时多走的这一步。
还想重复一遍:*p++就是*(p++),它们除了可读性之外没有任何区别,所以那种认为加上括号就可以实现先加1再解引用的想法是错误的。要达到那样的效果,可以用“*++p”。
(三)x > y ? 100 : ++y > 2 ? 20 : 30
这个表达式看起来有点吓人。让我们先给出更多的上下文吧:
int x = 3;
int y = 2;
int z = x > y ? 100 : ++y > 2 ? 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%2B%2B
[PS-2] 曾在新浪微博上见benbearchen提到有的公司在代码规范中要求:如果while的循环体为空语句,那么必需以continue语句代替,不准只写一个分号。我本人很赞成这个。上面strcpy和strlen的两个例子之所以没那么用,只是为了“随大流”,因为这两个函数的示例实现,许多人、许多书上都这么写。
优先级决定表达式中各种不同的运算符起作用的优先次序,而结合性则在相邻的运算符的具有同等优先级时,决定表达式的结合方向。
(一)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函数的示例代码随处都能见到:
size_t strlen(const char* str){ const char* p = str; while(*p++); return p - str - 1; }
注意上面函数中最后的减1。虽然是否退出while循环是由p的当前值解引用决定的,但即使while要退出,在“正式”退出之前,后自增(“++”)加1的副作用还是要体现。也可以这么理解:所谓“退出循环”,是指“不再执行循环体”,但控制表达式并非循环体的一部分,它的所有副作用在整个表达式结束之前都会生效。所以,我们最后要减掉循环退出时多走的这一步。
还想重复一遍:*p++就是*(p++),它们除了可读性之外没有任何区别,所以那种认为加上括号就可以实现先加1再解引用的想法是错误的。要达到那样的效果,可以用“*++p”。
(三)x > y ? 100 : ++y > 2 ? 20 : 30
这个表达式看起来有点吓人。让我们先给出更多的上下文吧:
int x = 3;
int y = 2;
int z = x > y ? 100 : ++y > 2 ? 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%2B%2B
[PS-2] 曾在新浪微博上见benbearchen提到有的公司在代码规范中要求:如果while的循环体为空语句,那么必需以continue语句代替,不准只写一个分号。我本人很赞成这个。上面strcpy和strlen的两个例子之所以没那么用,只是为了“随大流”,因为这两个函数的示例实现,许多人、许多书上都这么写。
相关文章推荐
- 说说C语言运算符的“优先级”与“结合性”
- 说说C语言运算符的“优先级”与“结合性”
- 说说C语言运算符的“优先级”与“结合性”
- [转]说说C语言运算符的“优先级”与“结合性”
- C语言运算符的优先级与结合性
- C语言运算符优先级与结合性
- C语言运算符优先级与结合性
- C语言运算符优先级和结合性
- C语言运算符的优先级和结合性实例详解
- c语言运算符优先级,结合性(左/右结合详解)
- C语言运算符优先级和结合性表
- C语言运算符的优先级和结合性
- C语言运算符优先级与结合性
- c++基础---之C语言运算符优先级及结合性
- C语言运算符的优先级表与结合性要点
- C语言运算符的优先级和结合性一览表
- c语言运算符的优先级和结合性
- C语言运算符的优先级和结合性一览表
- C语言运算符优先级和结合性预览表
- c语言运算符优先级,结合性(左/右结合详解)