C++條件運算符 a ? b : c ; 是右結合的,但是這個右結合要怎么理解呢?
對於a ? b : c ? d : e; 這樣的表達式如果按照右結合來解讀的話,那不應該是先運算c,然后返回d或者e,返回后再參與到a ? b : d / e;這樣的運算的嗎?
但實際代碼的編譯測試的結果顯然大家都已經知道並非如此,是先計算a,或者返回b,或者返回 (c ? d : e)這個整體的結果。
為什么是先計算a,而不是先計算c?右結合到底要怎么理解?網上的回答基本上都是錯誤的,個別的說法是對的,但是不完全准確,下面就具體說說這個右結合的理解。
先來看三個例子:
1. a + b*c - d; 2. a = b = c; 3. f()*g() / g();
上面的這三個例子分別涉及了表達式運算符的優先級、結合律、求值順序。
對於第一個例子,因為乘法運算符(*)的優先級比加減運算符("+","-")的優先級高,所以乘法優先,乘法結合的更緊密,故而第一個例子相當於 a + (b*c) - d;
對於第二個例子,同一個運算對象b,有兩個賦值運算符("="),同一運算符或者屬於同一組的運算符的優先級是相等的,這時就無法根據優先級來判斷誰優先,誰結合緊密,因此需要另外的東西來判定,這就是結合律。
結合律只用於表達式中同一個對象左右出現兩個相同優先級的操作符的情況,用於消除歧義。相當於添加()來調節優先級
根據結合律來判斷誰優先結合或者結合的更緊密,由於賦值運算符("=")是右結合的,因此右邊優先,將b及其右邊視為一個整體,從右向左計算,即 a = b = c;相當於 a = (b = c);而b=c;又是一個子表達式,先對子表達式進行運算,即將c的值賦給b,賦值運算完畢后返回賦值運算符左側的對象,即返回b,然后繼續參與a=b;的運算。
同一組運算符的意思是指幾個運算符優先級相同,屬於一個分組,C++中將不同優先級分成了若干組,比如"+"和"-"就屬於同一組。
從第一個和第二個例子可以看到,結合律確定表達式計算方向。第一個例子左結合,從左向右計算,第二個例子右結合,從右向左計算。
對於第三個例子,是兩個優先級相同,但是是不同的運算符,根據結合律從左向右結合,進行計算。這里就會出現2個名詞,一個是表達式的計算順序,或者叫表達式的計算方向,另一個叫運算對象的求值順序。在第三個例子當中,由於是左結合的,因此表達式的計算方向是從左往右。但是畢竟f( )和g( )是兩個函數,參與運算的是函數的返回值,而不是函數,因此需要事先對這兩個函數進行調用,調用完畢后,將返回值拿過來參與運算,假設f( )和g( )的返回值分別是f和g,即最終參與表達式運算的是f * g / g,這里確定了是返回值f先與返回值g相乘,然后再與返回值g做除法運算。但是有沒有規定先調用f( )呢?沒有!!!沒有!!!沒有!!!重要的事情說三遍,C++只確定了表達式的計算方向,並沒有規定要先獲取哪個參與運算的對象。
到這里,我們已經知道了表達式的兩個行為特征了,如下:
1. 復合表達式是會考慮優先級和結合律的。
2. 運算對象的求值順序與優先級、結合律沒有關系。
大多數運算符都沒有規定表達式中運算對象的求值順序,對於互不影響的函數之間,這並沒有什么問題,但如果這幾個函數共同影響同一個全局變量就會出現問題。
因此在C++ Primer第五版的123頁中才會有這么一說:
“因為表達式的行為不可預知,因此不論編輯器生成什么樣的代碼程序都是錯誤的。”
是的,因為求值順序沒有規定,怎么樣都有可能,這樣的代碼即使語法毫無問題,他也是錯誤的!
所以有兩條經驗准則用於書寫復合表達式:
1. 拿不准的時候最好用括號來強制讓表達式的組合關系符合程序邏輯的要求。
2. 如果改變了某個運算對象的值,在表達式的其他地方不要再使用這個運算對象
OK,到目前為止,我似乎還沒說多少關於條件運算符。接下來,我們用以上了解到的內容來看一看條件運算符。
條件運算符是右結合的
上面的規定是毫無疑問的,那么按照上面的知識來理解,對於 a ? b : c ? d : e; 按照右結合來解讀先運算c,然后返回d或者e,返回后再參與到a ? b : d/e;是這樣嗎?
很顯然不是,為什么?開頭前面的例子都是左結合從左邊開始計算,右結合從右邊開始計算,為什么這個不是?
原因在於:前面的表達式中的運算符沒有規定運算對象求值順序,結合律只能在確定結合對象和計算方向后,按照結合性來計算表達式。但有四個是特例,這四個特殊的運算符規定了求值順序和計算方向,它們分別如下:
1. 邏輯與&&,先求左側對象,左側為真,再求右側,左側為假,則不再求右側
2. 邏輯或 || ,先求左側對象,左側為假,再求右側,左側為真,則不再求右側
3. 條件運算符 條件 ? 表達式1 : 表達式2 ,先對條件判斷,為真,對表達式1進行計算,為假,對表達式2進行計算
4. 逗號運算符“,”先求逗號運算符左側的值,然后再對表達式右側的求值。
其中,第一條和第二條的求值策略,我們給它一個術語,叫做:short-circuit-evaluation(短路求值)。
最后梳理一下,對於條件運算符,它是右結合的,對於 a ? b : c ? d : e這樣的符合表達式,將最右邊優先結合視為一個整體,相當於a ? b : (c ? d : e),但是並不是先對這個運算對象進行求值,如果沒有規定求值順序,可能先求b,也可能先求(c ? d : e),也可能先求a,然后再把a或b的最終結果或者(c ? d : e)的最終結果拿來從右向左開始參與表達式運算。也即運算對象求值不知道誰優先,但是表達式計算方向卻是從右先左的。
但是條件運算符規定了求值的順序和計算方向,必須先求條件a,然根據a的真假來求b或者(c ? d : e)。因此這里的右結合只起了怎么組合該復合表達式的作用,最終的求值順序和表達式計算方向被該運算符的規定指明了。