從一道IBM的筆試題看編碼規(guī)范
char* fun1() { cout<<"a"; return "1"; }
char* fun2() { cout<<"b"; return "1"; }
char* fun3() { cout<<"c"; return "1"; }
int main(int argc, char* argv[])
{
cout<<"m"<
return 0;
}
屏幕輸出是多少?
cbam111
為什么不是abcm111呢?或者是ma1b1c1呢?
其實(shí)問(wèn)題的實(shí)質(zhì)上述同一個(gè)語(yǔ)句中多個(gè)表達(dá)式的執(zhí)行順序問(wèn)題。對(duì)于<<其實(shí)**一般**是從右往左處理的,但實(shí)際上并沒(méi)有規(guī)定順序
cout<
即
operator<<(operator<<(operator<<(cout,fun1),fun2),fun3)
有點(diǎn)遞歸的感覺(jué),第一個(gè)〈〈需要后面的結(jié)果,最后就相當(dāng)于從后面開(kāi)始算起
于是碰到fun()必然先輸出字符,然后返回1,于是就變成了
cout<<"m"<
繼續(xù)往左走,直到 cout<<"m"<<1<<1<<1 ;的時(shí)候已經(jīng)輸出了cba,之后就是按順序輸出了m111, 所以看到的結(jié)果就是 cbam111
但是問(wèn)題就在這里:這是個(gè)<<表達(dá)式。<<本來(lái)是位運(yùn)算,但是這里cout卻是來(lái)利用運(yùn)算的“副作用”。所謂副作用,就是計(jì)算一個(gè)表達(dá)式的時(shí)候,除了得到它的值以外,對(duì)環(huán)境產(chǎn)生的影響都是副作用。
比如:
int a=1,b=2,c=3,d;
d=a<
這一步,a<
但是,cout<<"a"就不一樣了。這個(gè)表達(dá)式的值我們根本就不關(guān)心。我們只關(guān)心,這個(gè)表達(dá)式“計(jì)算”完以后,"a"被輸出到屏幕上了。這里“a被輸出到屏幕上”就是副作用。
再看這個(gè)例子:
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()都沒(méi)有副作用。因此,這個(gè)表達(dá)式,不論是先計(jì)算foo(a,b)的值,還是先計(jì)算bar(b,c)的值,都不會(huì)影響計(jì)算的結(jié)果。
但是,如果是這個(gè)例子:
int foo(int* a) { (*a)++; return *a;}
int bar(int* a) { (*a)--; return *a;}
int a=5,b;
b=foo(&a)+bar(&a);
這個(gè)表達(dá)式,foo()和bar()都有副作用,所以,先計(jì)算foo(&a)還是先計(jì)算bar(&a),將直接影響到b的值。
假如先計(jì)算foo,再計(jì)算bar。
首先,a=5
計(jì)算foo(&a),a變成6,foo(&a)的值是6
計(jì)算bar(&a),a變成5,bar(&a)的值是5
這樣,b=6+5=11
假如先計(jì)算bar,再計(jì)算foo。
首先,a=5
計(jì)算bar(&a),a變成4,foo(&a)的值是4
計(jì)算foo(&a),a變成5,bar(&a)的值是5
這樣,b=5+4=9
這就造成了計(jì)算結(jié)果不一致。
====
那。。。怎么辦
一般來(lái)說(shuō),編c/c++程序有一個(gè)紀(jì)律:一個(gè)語(yǔ)句中不要有兩個(gè)表達(dá)式有副作用。
典型的這類(lèi)行為包括:b=(a++)+(a++)+(a++);
這是典型的違反這條紀(jì)律的行為。每個(gè)a++都有副作用(改變a的值)。整個(gè)表達(dá)式的值跟求值順序直接相連。
還有就是
char* fun() { cout<<"q"; return ""; }
cout<<"m"<
每個(gè)fun()都有副作用(向屏幕上顯示字符)。因此效果直接與求值順序相關(guān)。(而整個(gè)表達(dá)式的值我們根本就不關(guān)心)。
======
在c/c++中,求值順序是怎么樣的?不知道。
C/C++的規(guī)范中,求值順序是不規(guī)定的。這是為了給編譯器以優(yōu)化的空間。
比如:b=(a+2)+(a+2);,那么如果只計(jì)算一次a+2的值,而不是兩次,那么計(jì)算量會(huì)大大降低。
因此,不要在C語(yǔ)言里面做這種事情:
char* fun() { cout<<"q"; return ""; }
cout<<"m"<
要這樣:
char* fun() { return "q"; }
cout<<"m"<
這樣更好:
string fun() { return string("q"); }
cout<<"m"<
這樣就更好了:
string f="q"; // 隱式轉(zhuǎn)換
cout<<"m"<
由上面的一系列分析可知,當(dāng)一個(gè)語(yǔ)句含有多個(gè)具備副作用的表達(dá)式時(shí),程序執(zhí)行的效果有時(shí)會(huì)依賴(lài)于各個(gè)表達(dá)式的執(zhí)行順序,而這種順序有時(shí)候又是和編譯器有一定的關(guān)系,尤其是優(yōu)化后可能帶來(lái)問(wèn)題,因此良好的編程規(guī)范應(yīng)該避免在同一個(gè)語(yǔ)句中對(duì)某個(gè)元素多次操作。