[c语言]运算符的优先级与结合性
c語言中運(yùn)算符的優(yōu)先級(jí)和結(jié)合性常常被人混淆一談,本文目的在于簡(jiǎn)單談?wù)剝烧叩膮^(qū)別。本文舉幾個(gè)簡(jiǎn)單的例子說明,這些運(yùn)算符也特別常用。
?
首先要明白的是:優(yōu)先級(jí)決定表達(dá)式中各種不同的運(yùn)算符起作用的優(yōu)先次序;而結(jié)合性則在相鄰的運(yùn)算符的具有同等優(yōu)先級(jí)時(shí),決定表達(dá)式的結(jié)合方向。
?
[賦值運(yùn)算符“=”]
對(duì)于賦值運(yùn)算符來說,常會(huì)用到的是連續(xù)賦值的表達(dá)式。比如“a=b=c”。
這里的變量b的兩邊都是賦值運(yùn)算,優(yōu)先級(jí)當(dāng)然是相同的,那么應(yīng)該怎么理解這個(gè)表達(dá)式呢?我們知道,賦值表達(dá)式具有“向右結(jié)合”的特性,這就表示這個(gè)表達(dá)式的語意結(jié)構(gòu)是“a=(b=c)”,而不是“(a=b)=c”。這意味著首先完成c向b賦值,然后將表達(dá)式“b=c”的值再賦給a。這個(gè)區(qū)別特別重要!因?yàn)榭赡軙?huì)涉及到強(qiáng)制類型轉(zhuǎn)換、初值不同等情況,所以不同的理解得到的答案是不一樣的。
這里我們?cè)賮砜匆话愕亩\(yùn)算符,為了說明方便,我們現(xiàn)在不妨記作@。如果它是“向左結(jié)合”的,那么表達(dá)式“x@y@z”表達(dá)的意思就應(yīng)該是“(x@y)@z”;如果是“向右結(jié)合”的,那么應(yīng)該表達(dá)的是“x@(y@z)”。這里值得注意的是,這里的二元運(yùn)算符可以不是同一種運(yùn)算符,只要有同等優(yōu)先級(jí),以上結(jié)論就是適用的。比如“a*b/c”表達(dá)的就是“(a*b)/c”。
?
[自增運(yùn)算符“++”與解引用運(yùn)算符“*”]
這一節(jié)我們以例子“*p++”引出。下面這個(gè)據(jù)說是爛大街的實(shí)現(xiàn)strcpy函數(shù)的示例代碼:
char* strcpy( char* dest, const char* src ){char*p = dest;while(*p++ = *src++);return dest; }?
我們很快發(fā)現(xiàn),理解這一小段程序的關(guān)鍵就在于怎么理解這個(gè)循環(huán)條件“*p++”的含義。
首先,解引用運(yùn)算符“*”的優(yōu)先級(jí)低于后面的自增運(yùn)算符“++”,所以這個(gè)表達(dá)式在語義上等價(jià)于“*(p++)”,而不是“(*p)++”。這里從語義上來說,括號(hào)是多余的,當(dāng)然從程序的可讀性來說建議還是加上括號(hào)。
還有一個(gè)問題常讓人糊涂,就是自增運(yùn)算符“++”的語義。很多書上寫“后自增是先取值,后加1”。這樣講是沒有錯(cuò)的,但在一些特定的語境上容易讓人無解,比如上面這個(gè)while語句。
才開始學(xué)習(xí)的時(shí)候肯定有這樣的疑惑:當(dāng)一個(gè)表達(dá)式同時(shí)包含自增、解引用、賦值,且最終作為控制循環(huán)的條件的時(shí)候,這里的“前取值”到底“先”到什么程度呢?這時(shí)候我們需要查閱一下c語言標(biāo)準(zhǔn)。以下摘自C99標(biāo)準(zhǔn):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.
也就是說,后自增表達(dá)式的結(jié)果值就是被自增之前的那個(gè)值,然后這個(gè)結(jié)果值被確定之后,操作數(shù)的值會(huì)被自增。而這種“自增”的副作用會(huì)在上一個(gè)“序列點(diǎn)”跟下一個(gè)“序列點(diǎn)”之間完成。
本文不打算詳細(xì)討論序列點(diǎn)。有興趣的讀者可以閱讀一下標(biāo)準(zhǔn)。需要指出的是:賦值運(yùn)算在C語言中并不是一個(gè)序列點(diǎn),所以,上面的while語句中,src的自增效果無需是在賦值之前完成。但while的整個(gè)控制表達(dá)式的結(jié)束卻是一個(gè)序列點(diǎn)。
我們可以這樣解讀“while(*p++=*src++);”:首先while的條件變量是一個(gè)賦值表達(dá)式,左側(cè)操作數(shù)是“*p++”,右側(cè)操作數(shù)是“*src++”,整個(gè)表達(dá)式的值將是賦值完成后左側(cè)項(xiàng)的值。而左右兩側(cè)是對(duì)兩個(gè)后自增表達(dá)式解引用,由前面的說明可以知道,解引用作用于整個(gè)后自增表達(dá)式而不僅僅作用于p或src本身,那么根據(jù)上面引用的標(biāo)準(zhǔn),他們“取用”的人別是指針p和src的當(dāng)前值。而自增的副作用只需要在下一個(gè)序列點(diǎn)之前完成即可。
簡(jiǎn)單地說,編譯器分別取得指針p和src的當(dāng)前值,基于這個(gè)值完成“*src”向“*p”的賦值;同時(shí)這個(gè)賦值結(jié)果也將作為整個(gè)賦值表達(dá)式的值,用來決定是否退出循環(huán)。然后,在整個(gè)表達(dá)式結(jié)束時(shí)的某一個(gè)時(shí)刻(在不影響之前敘述的前提下),p和src人別加1。
也就是說,我們基于p和src的舊值所進(jìn)行賦值和循環(huán)條件判斷,然后完成p和src的自增。
另外,這里有關(guān)于后自增(后自減)運(yùn)算的另外兩種表述,雖然與c語言標(biāo)準(zhǔn)上的說法并不完全一致,但在最終的語義效果如出一轍:
(1)后自增“x++”相當(dāng)于一個(gè)逗號(hào)表達(dá)式:“tmp=x,++x,tmp”;
(2)后自增就是把操作數(shù)加1,然后返回加1之前的值作為整個(gè)表達(dá)式的值。
這里值得一提的是,在c++語言中需要重載后自增運(yùn)算符時(shí),往往采用的機(jī)制就是基于這兩種說法。
再舉一個(gè)據(jù)說還是爛大街的實(shí)現(xiàn):
size_t strlen(const char* str){const char* p = str;while(*p++);return p - str - 1; }?
我們發(fā)現(xiàn)函數(shù)最后有一個(gè)減1的操作,這是因?yàn)楫?dāng)循環(huán)條件不滿足而退出循環(huán)時(shí),會(huì)在“正式”退出之前,后自增運(yùn)算符“++”加1的副作用。可以這么理解:所謂“退出循環(huán)”,指的是“不再執(zhí)行循環(huán)體”,但控制表達(dá)式并不是循環(huán)體的一部分,它的所有副作用在整個(gè)表達(dá)式結(jié)束之前都會(huì)生效。
這一節(jié)的最后,重要的事情再說一遍:*p++就是*(p++),兩者除了可讀性以外沒有任何區(qū)別。那種認(rèn)為加上括號(hào)就可以實(shí)現(xiàn)先加1再解引用的想法是錯(cuò)誤的,要想實(shí)現(xiàn)那樣的效果,可以用“*++p”。
?
[三目元算符“ ? : ”]
先給出一個(gè)例子:
int x = 3; int y = 2; int z = x > y ? 100 : ++y > 2 ? 20 : 30;?
我們會(huì)關(guān)心z的值是多少。
這里是兩個(gè)三目運(yùn)算符的嵌套,有“向右結(jié)合”的特性。許多人認(rèn)為基于這個(gè)性質(zhì),右側(cè)的內(nèi)層條件運(yùn)算“++y>2?20:30”應(yīng)該先求值。即y先加1,大于2的條件成立,從而使這個(gè)表達(dá)式取得結(jié)果“20”;然后求整個(gè)表達(dá)式的值,這時(shí)y的值是3,所以“x>y”為假,故整個(gè)結(jié)果是剛剛求得的20。
然而事實(shí)并不是這樣…… 這種思路是錯(cuò)誤的!!!
這里的錯(cuò)誤在于:把優(yōu)先級(jí)、結(jié)合性與求值次序完全混為一談。
首先,在大多數(shù)情況下,c語言對(duì)表達(dá)式中各個(gè)子表達(dá)式的求值次序并沒有嚴(yán)格的規(guī)定;其次,即使是求值次序確定的場(chǎng)合,也是要先確定了表達(dá)式的語意結(jié)構(gòu),在獲得確定的語義之后才談得上“求值次序”。
對(duì)于上面的例子,條件運(yùn)算符“向右結(jié)合”這一個(gè)特性,并沒有決定內(nèi)層的條件表達(dá)式先被求值,而是決定了上面表達(dá)式的語意結(jié)構(gòu)等價(jià)于“x>y?100:(++y>2?20:30)”,而不是“(x>y?100:++y)>2?20:30”。這才是“向右結(jié)合”的真正含義。
編譯器確定了表達(dá)式的結(jié)構(gòu)之后,就可以準(zhǔn)確地為它產(chǎn)生運(yùn)行時(shí)的行為了。條件運(yùn)算符是c語言中為數(shù)不多的對(duì)求值次序有著明確規(guī)定的運(yùn)算符之一(另外還有三個(gè),分別是邏輯與“&&”、邏輯或“||”和逗號(hào)運(yùn)算符“,”)。
c語言規(guī)定:條件表達(dá)式首先對(duì)條件部分求值,如果條件部分為真,則對(duì)問號(hào)之后冒號(hào)之前的部分求值(表達(dá)式2),并將求得的結(jié)果作為整個(gè)表達(dá)式的值;否則對(duì)冒號(hào)之后的部分(表達(dá)式3)求值并作為整個(gè)表達(dá)式的值。
因此,對(duì)于表達(dá)式“x>y?100:(++y>2?20:30)”,首先看x大于y是否成立,在本例中它是成立的,因此整個(gè)表達(dá)式的值為100。也就是說,表達(dá)式3根本就不會(huì)被執(zhí)行,其中包含的自增運(yùn)算符的副作用也不會(huì)生效。
?
[最后再說幾句]
本文主要闡述了以下幾點(diǎn):
(1)優(yōu)先級(jí)決定表達(dá)式中各種不同的運(yùn)算符起作用的優(yōu)先次序,而結(jié)合性則在相鄰的兩個(gè)運(yùn)算符的具有同等優(yōu)先級(jí)時(shí),決定表達(dá)式的結(jié)合方向;
(2)后自增(后自減)從語義效果上可以理解為在做完自增(自減)之后,返回自增(自減)之前的值作為整個(gè)表達(dá)式的結(jié)果值;
(3)準(zhǔn)確來講,優(yōu)先級(jí)和結(jié)合性確定了表達(dá)式的語義結(jié)構(gòu),不能跟求值次序混為一談。
?
PS.
1、本文參考博文:http://blog.csdn.net/steedhorse/article/details/5903974
2、維基百科上有C/C++語言運(yùn)算符表:http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B
3、曾在新浪微博上見benbearchen提到有的公司在代碼規(guī)范中要求:如果while的循環(huán)體為空語句,那么必需以continue語句代替,不準(zhǔn)只寫一個(gè)分號(hào)。我本人很贊成這個(gè)。上面strcpy和strlen的兩個(gè)例子之所以沒那么用,只是為了“隨大流”,因?yàn)檫@兩個(gè)函數(shù)的示例實(shí)現(xiàn),許多人、許多書上都這么寫。
?
轉(zhuǎn)載于:https://www.cnblogs.com/CQBZOIer-zyy/p/5303741.html
總結(jié)
以上是生活随笔為你收集整理的[c语言]运算符的优先级与结合性的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: serialization机制
- 下一篇: bkwin设置文本控件为多行模式