下面是IBM的一道筆試題
#include <iostream>
using namespace std;
int fun( )
{
cout << "f" ;
return 1;
}
int main()
{
int i = 1;
// cout << i++ << i++ << i++ << endl;
cout << "m" << fun() << fun() << fun() << endl;
return 1;
}
輸出fffm111
問題:cout這種連接寫法的輸出的執行順序是啥呢?
cout<<"m"<<fun1()<<fun2()<<fun3()<<endl;
<<運算是左結合的。
必然先求cout<<"m"的值,值仍然是cout
然後試圖求cout<<fun1()的值,這必須先求出fun1()的值。整個表達式的值仍然是cout
然後試圖求cout<<fun2()的值,這必須先求出fun2()的值。整個表達式的值仍然是cout
然後試圖求cout<<fun3()的值,這必須先求出fun3()的值。整個表達式的值仍然是cout
最後是cout<<endl的值,值是cout
整個表達式語句以分號結尾
注意:問題就在這裏:“這必須先求出fun1()的值”,“這必須先求出fun2()的值”,“這必須先求出fun3()的值”,這3句。這是計算<<運算的前提。只要分別在計算cout<<fun1(),cout<<fun2(),cout<<fun3(),之前完成就可以了。
因此,具體是先計算fun1()的值,還是先計算fun2()的值,還是先計算fun3()的值,還是先計算cout<<"m"的值,都不影響表達式的值。
問題就在這裏:
這是個<<表達式。<<本來是位運算,但是這裏cout卻是來利用運算的“副作用”。
所謂副作用,就是計算一個表達式的時候,除了得到它的值以外,對環境產生的影響都是副作用。
比如:
int a=1,b=2,c=3,d;
d=a<<b:
這一步,a<<b計算出1左移2位得到的結果。結果是4。也就是說,賦值表達式結束後,d的值變成4,其它地方都沒有改變。這就是說這個<<運算沒有副作用。
但是,cout<<"a"就不一樣了。這個表達式的值我們根本就不關心。我們只關心,這個表達式“計算”完以後,"a"被輸出到屏幕上了。這裏“a被輸出到屏幕上”就是副作用。
再看這個例子:
int foo(int a, int b) { return a+b; }
int bar(int a, int b) { return a-b; }
int a=1,b=2,c=3,d;
d=foo(a,b)+bar(b,d);
這裏,foo()和bar()都沒有副作用。因此,這個表達式,不論是先計算foo(a,b)的值,還是先計算bar(b,c)的值,都不會影響計算的結果。
但是,如果是這個例子:
int foo(int* a) { (*a)++; return *a;}
int bar(int* a) { (*a)--; return *a;}
int a=5,b;
b=foo(&a)+bar(&a);
這個表達式,foo()和bar()都有副作用,所以,先計算foo(&a)還是先計算bar(&a),將直接影響到b的值。
假如先計算foo,再計算bar。
首先,a=5
計算foo(&a),a變成6,foo(&a)的值是6
計算bar(&a),a變成5,bar(&a)的值是5
這樣,b=6+5=11
假如先計算bar,再計算foo。
首先,a=5
計算bar(&a),a變成4,foo(&a)的值是4
計算foo(&a),a變成5,bar(&a)的值是5
這樣,b=5+4=9
這就造成了計算結果不一致。
===
那。。。怎麼辦
一般來說,編c/c++程序有一個紀律:一個語句中不要有兩個表達式有副作用。
典型的這類行爲包括:b=(a++)+(a++)+(a++);
這是典型的違反這條紀律的行爲。每個a++都有副作用(改變a的值)。整個表達式的值跟求值順序直接相連。
還有就是
char* fun() { cout<<"q"; return ""; }
cout<<"m"<<fun()<<fun()<<fun()<<endl;
每個fun()都有副作用(向屏幕上顯示字符)。因此效果直接與求值順序相關。(而整個表達式的值我們根本就不關心。雖然我知道,值就是cout)。
======
在c/c++中,求值順序是怎麼樣的?
不知道。
C/C++的規範中,求值順序是不規定的。這是爲了給編譯器以優化的空間。
比如:
b=(a+2)+(a+2);,那麼如果只計算一次a+2的值,而不是兩次,那麼計算量會大大降低。
因此,
不要在C語言裏面做這種事情:
char* fun() { cout<<"q"; return ""; }
cout<<"m"<<fun()<<fun()<<fun()<<endl;
要這樣:
char* fun() { return "q"; }
cout<<"m"<<fun()<<fun()<<fun()<<endl; // 輸出的一定是 "mqqq\n"
這樣更好:
string fun() { return string("q"); }
cout<<"m"<<fun()<<fun()<<fun()<<endl; // 輸出的一定是 "mqqq\n"
這樣就更好了:
string f="q"; // 隱式轉換
cout<<"m"<<fun()<<fun()<<fun()<<endl; // 輸出的一定是 "mqqq\n"
但是這樣不好嗎?:
cout<<"mqqq"<<endl;
這應該只是個測驗。我相信IBM的軟件工程師們不會編出這種垃圾代碼的。
代碼稍微修改了下
#include <iostream>
using namespace std;
int fun(int i)
{
cout << "f"<<i;
return i;
}
int main()
{
int i = 1;
cout << i++ << i++ << i++ << endl;
cout << "m" << fun(1) << fun(2) << fun(3) << endl;
cin.get();
return 1;
}
輸出結果:
321
f3f2f1m123
如果只針對題來說的話,實際是這樣的
cout<<"m"<<fun()<<fun()<<fun();
對於<<其實是從右往左處理的。於是碰到fun()必然先輸出f,然後返回1,於是就變成了
cout<<"m"<<fun()<<fun()<<1;
繼續往左走,直到 cout<<"m"<<1<<1<<1 ;的時候已經輸出了fff ,之後就是按順序輸出了m111, 所以看到的結果就是 fffm111