Published using Google Docs
[C語言][20110210]《語法》表示式運算次序的迷思
Updated automatically every 5 minutes

本文引用至:http://ehome.hifly.to/showthread.php?postid=3288

《語法》表示式運算次序的迷思         

程式設計俱樂部的rukawa問

void main(void)

{

int a=0;

cout<<5+a<<":"<<++a<<endl;

}

run出來結果是 6:1 ?

不是應該是 5:1 嗎?

變成從右到左去做運算,是這樣說嗎?

coco 回覆

當然不是這樣說...

你那一個cout的表示式中用了三個operator: << , +, ++

如果你去查一下 C++ 的 operator precedence,就會知道

++ 優先於 +

+ 優先於 <<

所以 ++a 會最先做

5+a 次之

最後由左至右做所有的 << operator

一個運算式的子運算式的計值次序是未定義的。

cout<<5+a<<":"<<++a<<endl;

這式子的 5+a中的a值是未定,因為++a導致一個assignment的子運算式,這子運算式的計值次序是未定。

編譯器若可以應警告這種岐義性。可惜的是事實上是難做到完全的檢查。

程式員只能靠自己避免這種有問題的寫法。

程式設計俱樂部的rukawa問

++a或a++不是問題

但應該是先run 5+a 之後再run ++a吧?

因為一般書籍好像沒談到cout的執行順序,

coco 回覆

cout 是 class ostream的一個物件,由於重載了運算子,所以在表示式 cout<<....

中它是一個 運算元(Operand)。運算元有執行順序(運算優先權)嗎?

程式設計俱樂部的rukawa問

如果同一個值會有這種可能發生的問題的話,那就多一個值吧

void main (void)

{

int a=0,b=0;

cout<<++b<<":"<<5+a+b<<":"<<++a<<endl;

}

答案是1:6:1(還是從右邊至左運算ㄚ)

coco 回覆

請先理解我上面說的這一句話:

一個運算式的子運算式的計值次序是未定義的。

這樣的式子:cout<<++b<<":"<<5+a+b<<":"<<++a<<endl;

依運算子優先順序運算的過程(a,b初值為0),

第一步:計算 ++b,++a

cout<<1<<":"<<5+a+b<<":"<<1<<endl;

並產生 a=1,b=1的兩個子運算式(注意在這一步並不保證a,b已指派新值)

第二步:計算 5+a+b

注意,由於第一步所產生的兩個子運算式 ANSI C++ 並不保證何時完成(只能確定

整個表示式完成計值時會完成),所以這時的 a,b 可能為1(新值)也可能還是0(原值),其值在不同的表示式演算法會有所不同。所以在這一步的5+a+b的結果是未定的,暫以?x?表示,這一步的表示式可化為:

cout<<1<<":"<<?x?<<":"<<1<<endl;

第三步:由左至右計算 <<運算子

(其它的表示式雷同就不再贅述了)

同一套編譯器,當然其表示式的演算法是一致的,故其結果會如你所言,看起來

兩個子運算式是由右至左立即被計值的。事實上大部份的C/C++演算法抄來抄去

的結果,使得情況和你所試的會八九不離十。但程式師不能依靠編譯器的演算法

的計值次序來撰寫程式,這難保不會摔跤的!例如java就採用先進後出的子運算式

計值,所以用你所寫的幾個測試表示式,其結果可能和在VC上試的完全不一樣。

再說,既然ANSI C++不保證子運算式的計值次序,誰能保證將來的C++編譯器的

表示式演算法不會改變?

程式設計俱樂部的rukawa問

之前所說

++ 優先於 +

+ 優先於 <<

上述的<<好像是左移運算,而非cout<<的<<(應該算是cout的間隔碼吧)

這次換成printf

void main (void)

{

int a=0;

printf("%d:%d:%d\n",++a,++a,++a);

}

答案3:2:1

姑且不論c/c++應該如何運算之前程式,但很明顯VC++的compiler是

右到左運算的,只是不曉得Borland C++ builder或其他版本的c/c++

運算結果是如何?

coco 回覆

之前說的好像都沒吸收...

而問題卻像是骨牌,一個接一個倒下來...  

函式參數的計值次序又是另一個不太能類比的議題。

你似乎很固執於自己的測試結果,事實上我也不懷疑你的測試結果。

只是這類的問題,你得從基本的運算式觀念求解,而不是用測試來當真理。

這樣吧,我把觀念一步步釐清,若你到了那一步不能理解或認同就不用看下

去了

1

i++ 這樣的一個式子會產生兩個運算式:

0(i的舊值,主運算式計值的結果)和 i+=1(子運算式)

C/C++運算子的運算優先權並不包括子運算式。子運算式何時運算並沒有

硬性規定,這給了編譯器的運算式演算法有了較大的彈性空間。但無論如何,

子運算式一定會在敘述結束前完成運算。

2

一個像下面的程式,最後i等於多少?

i=0;

i=i++;

這在你使用的編譯器中測試會有一個固定的答案,你也可以用你的邏輯去合理

化它。

但這樣的式子在 C/C++ 的規定裡,結果 i 是未定義的!

因為++優先權高於=,所以i++會先求值得0(i的舊值),主運算式成了i=0,

但子運算式i+=1和主運算式的 i=0誰會先計值確是未定的。

最後計值的運算式決定了最後i的值,所以結果可能為0或1。

因此

i=i++;

是個錯誤的寫法,不應該出現在你的程中!

3

這是一個使用互斥或來做swap的運算式:

a^=b^=a^=b;//swap a & b

(詳閱 http://ehome.hifly.to/showthread.php?s=&threadid=46)

這在大部份的C/C++編譯器,看來都是對的。但由於產生的三個子運算

式,C/C++不保證其計值次序,所以運算式的寫法是錯的。

那麼要如何保證其運算次序?下面的三個運算子,C/C++保證其左邊的運算式

(包括子運算式)會先於右邊的運算式計值:

, || &&

其中的逗號(,)稱為循序運算子,使用它可以把上面的swap運算式改為正確的式子:

a^=b, b^=a, a^=b;

4

不要把"循序運算子"和分隔符號搞混了!像

printf("%d:%d:%d\n",++a,++a,++a);

中的逗號只是參數的”分隔符號”和上面說的循序運算子是兩碼子事。

C/C++也不保證”分隔符號”的左右運算式(或引數)誰會先被計值。通常C/C++會先pass

較後面的引數;而也通常先pass的引數會先計值。但通常不表示”必然”!程式師不應預設通常是成立的!

printf 中的三個++a引數誰會先計值並不保證,所以這種傳遞引數的式子,所傳的值是未定義的,所以式子的寫法是錯的!

                          

coco 在 08-29-2003 06:37 AM 編輯