看板 C_and_CPP 關於我們 聯絡資訊
本文完整收錄 purpose 君之原文,請詳細讀完原文再看我的回應。 ※ 引述《purpose (purpose)》之銘言: C++ Primer 4/e (P.170) 中,運算子優先序 (Operator Precedence) 如下表: ?: (conditional) expr ? expr : expr 右—結合性 = (assignment) lvalue = expr 右—結合性 , (comma) expr, expr 左—結合性 #### 考慮以下程式: int main() { 1 ? puts("123"), puts("456") : puts("789"), puts("ABC"); return 0; } 執行後: 123 456 ABC 眾所接知的是 ?: 是唯一的三元運算子,所以上面的運算中, 運算元I 必然是常數 1, 運算元II 只能是被夾在中間的運算式 puts("123"), puts("4567"), 運算元III 有兩種可能: puts("789") puts("789"), puts("ABC") 其中,第二種可能代表 puts(789) 先與 comma 綁定成一體, 成為 (puts("789"), puts("ABC")) 之後,才以運算元III 的身份參戰。 從結果來看,顯然 comma 運算子的優先次序,確實低於 ?: 運算子。 #### 接著把 , 改成 = (賦值),再次驗證 ?: 的優先地位。 #include <stdio.h> int main() { int a = 1, b = 2; 1 ? a = 55 : b = 66; printf("a = %d, b = %d", a, b); return 0; } 執行後 (C++ Compiler): a = 55, b = 2 執行後 (C Compiler): error C2106: '=' : 左運算元必須是左值 (l-value) 這裡就出現爭議了。 #### 假設: 其實 ?: 優先權高於 assignment (=) 那就應該跟上次的分析過程相同,其運算元III 同樣為 b,又判斷式為 true, 最終傳回運算元II 當作 result,再以此 result 去執行最後的 = 66, 即 if (true) { (a = 55) = 66 }; 又 C99 標準的規定是: A conditional expression does not yield an lvalue. 故 55 = 66 得到一個 C2106 編譯錯誤,也是合情合理的。 又 C++03 標準的規定是: If the second and third operands are lvalues and have the same type, the result is of that type and is an lvalue. 故 (a = 55) 可以得到同樣是 int 型態的左值,最後再跑 = 66 理論上,最後會讓 a 變成 66,但實際結果是 55。 可見「?: 優先權高於 =」這個假設是錯的。 假設: 其實 ?: 優先權跟 assignment (=) 相等 在這個假設之下,因為 b 參與 ?: 跟 = 兩個運算子,雙方優先權相等, 所以依照右結合性,運算元III 將是 (b = 66),最終結果吻合 C++ 編譯器 跑出來的結果。 但 C 編譯器說「左運算元必須是左值」,此錯誤在此假設下, 就變成無理取鬧了。 運算元II 中「a = 55」是左值; 運算元III 中「b = 66」 亦為左值。 可見「?: 優先權等於 =」這個假設應該也是錯的。 世界上根本就沒有 "運算子優先權表",也可以說人人都是... 在 C++03 標準中,有這麼一句話: The precedence of operators is not directly specified, but it can be derived from the syntax. 根據 C99 / C++03 兩標準,可知 conditional-expression: logical-or-expression logical-or-expression ? expression : ┌ C++ 標準:assignment-expression └ C99 標準:conditional-expression 重點就是,"assignment-運算式" 包含 "conditional-運算式",前者是後者的超集, 一個 "賦值運算式" 文法上可以替換成 "條件運算式",但反過來就不行。 所以 1 ? a = 55 : b = 66; 這個 statement 中, 可以把 b = 66 當成 "賦值運算式",最終使整個 statement, 在 C++ 文法下變成一組條件運算式。 但 C 的文法下,運算元III 要求是更狹窄的條件運算式,所以只能是 b 本身, 最終導致 a 的右值無法跟 66 進行運算的錯誤。 至於 comma 運算子,只能出現在 "賦值運算式" 的超集 "expression" 內: expression: assignment-expression expression , assignment-expression 已知 "expression" 可以轉成 "賦值運算式",但是 "賦值運算式" 無法長大 變成 "expression",故 ?: 的運算元III 不會有 , 的參與。 故 1 ? puts("123"), puts("456") : puts("789"), puts("ABC"); 的結果 會是印出 123 456 ABC 除非在 "expression" 左右兩邊加上 () 就會變成 primary-expression,就可視為 單純的運算元使用了。 關於 ?: 的總結 一、不當左值用 int a = 10; int b = 20; ( 1 ? (a = 30) : (b = 40) ) = 50; printf("a = %d, b = %d\n", a, b); 結果是 a = 50, b = 20 哪天心血來潮把這段 code 貼到 C 就編譯錯誤了; 在 C++,把 int b 改成 short int b 就又編譯錯誤了,不經改的東西很難用。 二、最好不要用 看別人程式碼,若運算式寫落落長,又沒加小括號, 這時看運算子優先權表格來判斷,還是很方便的,前提就是別碰到 ?: 根據經驗,只要 ?: 的冒號後面塞了一堆東西,就是傷眼睛的時候了。 賦值變數 = ? 數值1 : 數值2; 求絕對值函數( (a > b)? 數值1 : 數值2 ); 像這樣簡單的形式,則是無妨的。 -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 124.8.132.109
diabloevagto:還是分開寫的好 02/14 23:52
EntHeEnd:推 02/15 01:01
purincess:我剛剛跟我朋友說 my python is better than my c++ 02/15 01:27
purincess:and my javascript is better than my c 在此得到印證(? 02/15 01:27
scwg: (a = 55) = 66 踩到 double assignment, a 要是多少都可吧 02/15 03:25
scwg: grammar 那邊的分析是對的, 不過 一、不當左值用 一樣有 02/15 03:33
scwg: multiple assignment 的問題 02/15 03:33
感謝。 依據 C++03 在 5.17 Assignment operators 的內文中說: The result of the assignment operation is the value stored in the left operand after the assignment has taken place; the result is an lvalue. 所以 (a = 55) = 66 這樣的運算,在 C++ 環境下,a 應該可以得到 66。 同時,在 C 的環境下,總是會變成 invalid lvalue in assignment 編譯錯誤。 不知道這樣理解對嗎?謝謝。
cole945:推 ``世界上根本就沒有 "運算子優先權表"'' 這句.. 02/15 05:00
※ 編輯: purpose 來自: 124.8.132.109 (02/15 06:07)
DEATHX:世界上根本就沒有 "運算子優先權表" +1 02/15 08:59
Favonia:這問題超麻煩... orz 02/15 19:04
dSnAil:世界上根本就沒有 "運算子優先權表"+1 02/15 21:03
xxxx9659:記起來了 ?:運算式 不當左值使用 02/17 19:42
============================================================================== 請先詳細讀完上文,以下回應開始: 一點淺見與各位分享,若有錯誤請指正,並請海涵,謝謝。 先講結論: (一)在 C99 中「=」的結果不是 lvalue,但 C++03 中卻是 lvalue,此結果導致「?:」運算子的傳回值也是不同的。 (二)若「A?B:C」中的 C 中含有「=」的時候,GCC C++ Compiler 會將整個敘述視為是 assignment-expression,而不是 範圍較小的 conditional-expression,也就是說「A?B:C」 如果是「1 ? varA = 1 : varB = 2 ;」,其中的敘述C是 「varB = 2」,而不是「varB」,但我個人認為這不符合程設邏輯 ,如果有兩種選擇的話,編譯器應當選擇較小範圍感覺上較合理, 更何況「?:」運算子的順序還高過「=」運算子呢,這不知道算不 算 GCC 的 Bug?;另一方面,GCC C Compiler 則將 「1 ? varA = 1 : varB = 2 ;」中的敘述C視為「varB」,因此 整個敘述變成了先執行「?:」傳回了一個「不是lvalue」的值(注 意在 C++ 中傳回 lvalue!),然後將此值經由「=」賦予 2,然 而又因為此值不是 lvalue 因此編譯器回報錯誤(lvalue required as left operand of assignment)。 <註>在 Wikipedia (2013/02/26) 的 Operators in C and C++ 條目提到: ---------------------------------------------------------------------- The binding of operators in C and C++ is specified (in the corresponding Standards) by a factored language grammar, rather than a precedence table. This creates some subtle conflicts. For example, in C, the syntax for a conditional expression is: logical-OR-expression ? expression : conditional-expression while in C++ it is: logical-OR-expression ? expression : assignment-expression Hence, the expression: e = a < d ? a++ : a = d is parsed differently in the two languages. In C, this expression is a syntax error, but many compilers parse it as: e = ((a < d ? a++ : a) = d) which is a semantic error, since the result of the conditional-expression (which might be a++) is not an lvalue. In C++, it is parsed as: e = (a < d ? a++ : (a = d)) which is a valid expression. ---------------------------------------------------------------------- (三)誠如 purpose 君所言,C++03 中提到「The precedence of operators is not directly specified, but it can be derived from the syntax.」,我想這就是造就第二點的原因。例如 「i=0, i++, i++;」,值得注意的是「++」運算子的順序高於「,」 運算子以及「=」運算子,但根據 C++03 的說法 i 應當是 2! (四)個人認為在寫程式的時候,應當不要使用如此容易讓人疑惑, 甚至可能在某些實作環境也可能讓編譯器疑惑的語法;不才亦認為如 此作法並不會讓程式執行起來更快,因為編譯器的工作是將程式語言 敘述轉成機器碼,或許語法寫得複雜難懂,但結果卻跟簡單語法一樣 (何況現今許多編譯器都已經號稱有最佳化的功能了),所以這樣做 可說是得不償失,減少了移植性和可讀性、維護性。 (五)希望各位在討論這個主題的時候可以列出編譯的環境,因為在 各種實做環境說不定結果是不同的。 (六)以下提供兩個檔案,是我用以實驗的程式,請各位參考;註解 內是利用 GCC 4.7.2 編譯並執行的結果,並請有其他環境(MSVC, IntelCompiler)的朋友提供執行結果,謝謝。 ============ CODE START ============ /* --------------------------------------------------------- * Description: the Usage of '?:' Operators ( C++ version ) * Author: ShaDer(ptt.cc) (aka. ShaD) * License: General Public License version 2 (GPLv2) * Compiler: C++ (gcc-4.7.2) ----------------------------------------------------------*/ #include <iostream> using namespace std; int main() { int TelNum = +886-2-3366-3366; //-- This is NOT my number :) cout << TelNum << "\n"; //-- output test: -5848 int a, b, c; /* a = 9, b = 10; 1 ? a = 99 : b = 1010; cout << a << "\n"; // 99 cout << b << "\n"; // 10 */ /* a = 3, b = 4; (1 ? a = 33 : b) = 44; cout << a << "\n"; // 44(!!!) cout << b << "\n"; // 4 */ /* a = 5, b = 6; (1) ? (a = 55) : (b) = 66; cout << a << "\n"; // 55 cout << b << "\n"; // 6 */ /* a = 7, b = 8; 1 ? (a = 77) : b = 88; cout << a << "\n"; // 77 cout << b << "\n"; // 8 */ /* a = 5, b = 6, c = 1; 1 ? a = 55 : b = c++; cout << a << "\n"; // 55 cout << b << "\n"; // 6 cout << c << "\n"; // 1 a = 5, b = 6, c = 1; 1 ? a = 55 : b = ++c; cout << a << "\n"; // 55 cout << b << "\n"; // 6 cout << c << "\n"; // 1 */ /* a = 5, b = 6, c = 1; (1 ? a = 55 : b) = (c++); cout << a << "\n"; // 1 cout << b << "\n"; // 6 cout << c << "\n"; // 2 */ /* a = 5, b = 6, c = 1; (1 ? a = 55 : b++) = (c++); // Error: lvalue required as left operand of assignment cout << a << "\n"; cout << b << "\n"; cout << c << "\n"; */ /* a = 5, b = 6, c = 1; (1 ? a = 55 : (b++)) = (c++); // Error: lvalue required as left operand of assignment cout << a << "\n"; cout << b << "\n"; cout << c << "\n"; */ return 0; } ============= CODE END ============= ============ CODE START ============ /* --------------------------------------------------------- * Description: the Usage of '?:' Operators ( C version ) * Author: ShaDer(ptt.cc) (aka. ShaD) * License: General Public License version 2 (GPLv2) * Compiler: C/C++ (gcc-4.7.2) ----------------------------------------------------------*/ #include <stdio.h> int main(void) { int a, b; a = 1, b = 2; 1 ? (a = 11) : (b = 22) ; printf("a=%d, b=%d\n", a, b); /* C: a = 11, b = 2 */ /* C++: a = 11, b = 2 */ a = 3, b = 4; 1 ? a = 33 : (b = 44) ; printf("a=%d, b=%d\n", a, b); /* C: a = 33, b = 4 */ /* C++: a = 33, b = 4 */ /********************************************************************** | 敘述『(1 ? (a = 55) : b ) = 66;』不符合 C99 規則,但 C++03 則通過, | | 原因應來自 assignment operators 在 C++03 的結果是 lvalue,但在 C99 | | 卻不是 lvalue,此結果導致「?:」運算子的傳回值也是不同的。 | **********************************************************************/ a = 5, b = 6; (1 ? a = 55 : b ) = 66 ; printf("a=%d, b=%d\n", a, b); /* C: ERROR: lvalue required as left operand of assignment */ /* C++: a = 66, b = 6 */ a = 7, b = 8; 1 ? (a = 77) : b = 88 ; printf("a=%d, b=%d\n", a, b); /* C: ERROR: lvalue required as left operand of assignment */ /* C++: a = 77, b = 8 */ a = 9, b = 10; 1 ? a = 99 : b = 1010 ; printf("a=%d, b=%d\n", a, b); /* C: ERROR: lvalue required as left operand of assignment */ /* C++: a = 99, b = 10 */ return 0; } ============= CODE END ============= -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 111.251.73.137 ※ 編輯: ShaDer 來自: 111.251.73.137 (02/26 11:02) ※ 編輯: ShaDer 來自: 111.251.73.137 (02/26 11:10) ※ 編輯: ShaDer 來自: 111.251.73.137 (02/26 11:15) ※ 編輯: ShaDer 來自: 111.251.73.137 (02/26 11:39)
uranusjr:?: 的用途是簡化語法, 如果還寫得落落長就失去意義了 02/26 11:47
xxxx9659:請問各位 這樣寫在任何的 C 或是 C++ 都是對的嗎? 03/01 21:38
xxxx9659:(2 > 1 ? varA : varB ) = 20; 03/01 21:38
xxxx9659:如果有再哪一版的 C / C++ 可能會錯那 我就不這樣寫了 03/01 21:38
purpose:http://codepad.org/tJlltMYs C++ 成功 03/01 21:49
purpose:http://codepad.org/IfvXRc0w C++ 失敗 03/01 21:50
purpose:當左值就是這麼難用 就是這麼難用 就是這麼難用 03/01 21:50
xxxx9659:大大不要激動XD 保險起見不要用就是了 03/01 22:00