日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發布時間:2024/4/18 编程问答 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 说说C语言运算符的“优先级”与“结合性” 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文鏈接:https://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函數的示例代碼隨處都能見到:

char* strcpy( char* dest, const char* src ){ char*p = dest; while(*p++ = *src++); return dest; }
理解這一實現的關鍵在于理解“*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實現也就沒什么問題了:

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语言运算符的“优先级”与“结合性”的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。