[c语言]运算符的优先级与结合性
c語言中運算符的優先級和結合性常常被人混淆一談,本文目的在于簡單談談兩者的區別。本文舉幾個簡單的例子說明,這些運算符也特別常用。
?
首先要明白的是:優先級決定表達式中各種不同的運算符起作用的優先次序;而結合性則在相鄰的運算符的具有同等優先級時,決定表達式的結合方向。
?
[賦值運算符“=”]
對于賦值運算符來說,常會用到的是連續賦值的表達式。比如“a=b=c”。
這里的變量b的兩邊都是賦值運算,優先級當然是相同的,那么應該怎么理解這個表達式呢?我們知道,賦值表達式具有“向右結合”的特性,這就表示這個表達式的語意結構是“a=(b=c)”,而不是“(a=b)=c”。這意味著首先完成c向b賦值,然后將表達式“b=c”的值再賦給a。這個區別特別重要!因為可能會涉及到強制類型轉換、初值不同等情況,所以不同的理解得到的答案是不一樣的。
這里我們再來看一般的二元運算符,為了說明方便,我們現在不妨記作@。如果它是“向左結合”的,那么表達式“x@y@z”表達的意思就應該是“(x@y)@z”;如果是“向右結合”的,那么應該表達的是“x@(y@z)”。這里值得注意的是,這里的二元運算符可以不是同一種運算符,只要有同等優先級,以上結論就是適用的。比如“a*b/c”表達的就是“(a*b)/c”。
?
[自增運算符“++”與解引用運算符“*”]
這一節我們以例子“*p++”引出。下面這個據說是爛大街的實現strcpy函數的示例代碼:
char* strcpy( char* dest, const char* src ){char*p = dest;while(*p++ = *src++);return dest; }?
我們很快發現,理解這一小段程序的關鍵就在于怎么理解這個循環條件“*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”的賦值;同時這個賦值結果也將作為整個賦值表達式的值,用來決定是否退出循環。然后,在整個表達式結束時的某一個時刻(在不影響之前敘述的前提下),p和src人別加1。
也就是說,我們基于p和src的舊值所進行賦值和循環條件判斷,然后完成p和src的自增。
另外,這里有關于后自增(后自減)運算的另外兩種表述,雖然與c語言標準上的說法并不完全一致,但在最終的語義效果如出一轍:
(1)后自增“x++”相當于一個逗號表達式:“tmp=x,++x,tmp”;
(2)后自增就是把操作數加1,然后返回加1之前的值作為整個表達式的值。
這里值得一提的是,在c++語言中需要重載后自增運算符時,往往采用的機制就是基于這兩種說法。
再舉一個據說還是爛大街的實現:
size_t strlen(const char* str){const char* p = str;while(*p++);return p - str - 1; }?
我們發現函數最后有一個減1的操作,這是因為當循環條件不滿足而退出循環時,會在“正式”退出之前,后自增運算符“++”加1的副作用。可以這么理解:所謂“退出循環”,指的是“不再執行循環體”,但控制表達式并不是循環體的一部分,它的所有副作用在整個表達式結束之前都會生效。
這一節的最后,重要的事情再說一遍:*p++就是*(p++),兩者除了可讀性以外沒有任何區別。那種認為加上括號就可以實現先加1再解引用的想法是錯誤的,要想實現那樣的效果,可以用“*++p”。
?
[三目元算符“ ? : ”]
先給出一個例子:
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語言規定:條件表達式首先對條件部分求值,如果條件部分為真,則對問號之后冒號之前的部分求值(表達式2),并將求得的結果作為整個表達式的值;否則對冒號之后的部分(表達式3)求值并作為整個表達式的值。
因此,對于表達式“x>y?100:(++y>2?20:30)”,首先看x大于y是否成立,在本例中它是成立的,因此整個表達式的值為100。也就是說,表達式3根本就不會被執行,其中包含的自增運算符的副作用也不會生效。
?
[最后再說幾句]
本文主要闡述了以下幾點:
(1)優先級決定表達式中各種不同的運算符起作用的優先次序,而結合性則在相鄰的兩個運算符的具有同等優先級時,決定表達式的結合方向;
(2)后自增(后自減)從語義效果上可以理解為在做完自增(自減)之后,返回自增(自減)之前的值作為整個表達式的結果值;
(3)準確來講,優先級和結合性確定了表達式的語義結構,不能跟求值次序混為一談。
?
PS.
1、本文參考博文:http://blog.csdn.net/steedhorse/article/details/5903974
2、維基百科上有C/C++語言運算符表:http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B
3、曾在新浪微博上見benbearchen提到有的公司在代碼規范中要求:如果while的循環體為空語句,那么必需以continue語句代替,不準只寫一個分號。我本人很贊成這個。上面strcpy和strlen的兩個例子之所以沒那么用,只是為了“隨大流”,因為這兩個函數的示例實現,許多人、許多書上都這么寫。
?
轉載于:https://www.cnblogs.com/CQBZOIer-zyy/p/5303741.html
總結
以上是生活随笔為你收集整理的[c语言]运算符的优先级与结合性的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: serialization机制
- 下一篇: bkwin设置文本控件为多行模式