第4章 表達(dá)式和基本語句
讀者可能懷疑:連if、for、while、goto、switch這樣簡單的東西也要探討編程風(fēng)格,是不是小題大做?
我真的發(fā)覺很多程序員用隱含錯(cuò)誤的方式寫表達(dá)式和基本語句,我自己也犯過類似的錯(cuò)誤。
表達(dá)式和語句都屬于C++/C的短語結(jié)構(gòu)語法。它們看似簡單,但使用時(shí)隱患比較多。本章歸納了正確使用表達(dá)式和語句的一些規(guī)則與建議。
4.1 運(yùn)算符的優(yōu)先級
C++/C語言的運(yùn)算符有數(shù)十個(gè),運(yùn)算符的優(yōu)先級與結(jié)合律如表4-1所示。注意一元運(yùn)算符 + - * 的優(yōu)先級高于對應(yīng)的二元運(yùn)算符。
優(yōu)先級
|
運(yùn)算符
|
結(jié)合律
|
從
高
到
低
排
列
|
(
) [ ]
-> .
|
從左至右
|
! ~
++ -- (類型)
sizeof
+ -
* &
|
從右至左
|
* / %
|
從左至右
|
+ -
|
從左至右
|
<< >>
|
從左至右
|
< <=
> >=
|
從左至右
|
== !=
|
從左至右
|
&
|
從左至右
|
^
|
從左至右
|
|
|
從左至右
|
&&
|
從左至右
|
||
|
從右至左
|
?:
|
從右至左
|
= +=
-= *= /=
%= &= ^=
|= <<=
>>=
|
從左至右
|
表4-1 運(yùn)算符的優(yōu)先級與結(jié)合律
l
【規(guī)則4-1-1】如果代碼行中的運(yùn)算符比較多,用括號(hào)確定表達(dá)式的操作順序,避免使用默認(rèn)的優(yōu)先級。
由于將表4-1熟記是比較困難的,為了防止產(chǎn)生歧義并提高可讀性,應(yīng)當(dāng)用括號(hào)確定表達(dá)式的操作順序。例如:
word = (high
<< 8) | low
if ((a | b)
&& (a & c))
4.2 復(fù)合表達(dá)式
如 a = b = c = 0這樣的表達(dá)式稱為復(fù)合表達(dá)式。允許復(fù)合表達(dá)式存在的理由是:(1)書寫簡潔;(2)可以提高編譯效率。但要防止濫用復(fù)合表達(dá)式。
l
【規(guī)則4-2-1】不要編寫太復(fù)雜的復(fù)合表達(dá)式。
例如:
i = a >= b && c < d &&
c + f <= g + h ; // 復(fù)合表達(dá)式過于復(fù)雜
l
【規(guī)則4-2-2】不要有多用途的復(fù)合表達(dá)式。
例如:
d = (a = b + c) + r ;
該表達(dá)式既求a值又求d值。應(yīng)該拆分為兩個(gè)獨(dú)立的語句:
a = b + c;
d = a + r;
l
【規(guī)則4-2-3】不要把程序中的復(fù)合表達(dá)式與“真正的數(shù)學(xué)表達(dá)式”混淆。
例如:
if (a < b < c) // a < b < c是數(shù)學(xué)表達(dá)式而不是程序表達(dá)式
并不表示
if ((a<b) && (b<c))
而是成了令人費(fèi)解的
if ( (a<b)<c )
if語句是C++/C語言中最簡單、最常用的語句,然而很多程序員用隱含錯(cuò)誤的方式寫if語句。本節(jié)以“與零值比較”為例,展開討論。
4.3.1 布爾變量與零值比較
l
【規(guī)則4-3-1】不可將布爾變量直接與TRUE、FALSE或者1、0進(jìn)行比較。
根據(jù)布爾類型的語義,零值為“假”(記為FALSE),任何非零值都是“真”(記為TRUE)。TRUE的值究竟是什么并沒有統(tǒng)一的標(biāo)準(zhǔn)。例如Visual C++ 將TRUE定義為1,而Visual Basic則將TRUE定義為-1。
假設(shè)布爾變量名字為flag,它與零值比較的標(biāo)準(zhǔn)if語句如下:
if (flag) // 表示flag為真
if (!flag) // 表示flag為假
其它的用法都屬于不良風(fēng)格,例如:
if (flag
== TRUE)
if (flag
== 1 )
if (flag
== FALSE)
if (flag
== 0)
4.3.2 整型變量與零值比較
l
【規(guī)則4-3-2】應(yīng)當(dāng)將整型變量用“==”或“!=”直接與0比較。
假設(shè)整型變量的名字為value,它與零值比較的標(biāo)準(zhǔn)if語句如下:
if (value == 0)
if (value != 0)
不可模仿布爾變量的風(fēng)格而寫成
if (value) //
會(huì)讓人誤解 value是布爾變量
if (!value)
4.3.3 浮點(diǎn)變量與零值比較
l
【規(guī)則4-3-3】不可將浮點(diǎn)變量用“==”或“!=”與任何數(shù)字比較。
千萬要留意,無論是float還是double類型的變量,都有精度限制。所以一定要避免將浮點(diǎn)變量用“==”或“!=”與數(shù)字比較,應(yīng)該設(shè)法轉(zhuǎn)化成“>=”或“<=”形式。
假設(shè)浮點(diǎn)變量的名字為x,應(yīng)當(dāng)將
if (x == 0.0) //
隱含錯(cuò)誤的比較
轉(zhuǎn)化為
if ((x>=-EPSINON) &&
(x<=EPSINON))
其中EPSINON是允許的誤差(即精度)。
4.3.4 指針變量與零值比較
l
【規(guī)則4-3-4】應(yīng)當(dāng)將指針變量用“==”或“!=”與NULL比較。
指針變量的零值是“空”(記為NULL)。盡管NULL的值與0相同,但是兩者意義不同。假設(shè)指針變量的名字為p,它與零值比較的標(biāo)準(zhǔn)if語句如下:
if
(p == NULL) // p與NULL顯式比較,強(qiáng)調(diào)p是指針變量
if
(p != NULL)
不要寫成
if (p == 0) // 容易讓人誤解p是整型變量
if (p != 0)
或者
if (p) //
容易讓人誤解p是布爾變量
if (!p)
4.3.5 對if語句的補(bǔ)充說明
有時(shí)候我們可能會(huì)看到
if (NULL == p) 這樣古怪的格式。不是程序?qū)戝e(cuò)了,是程序員為了防止將 if (p == NULL) 誤寫成 if (p = NULL),而有意把p和NULL顛倒。編譯器認(rèn)為 if (p
= NULL) 是合法的,但是會(huì)指出 if (NULL = p)是錯(cuò)誤的,因?yàn)?span lang="EN-US">NULL不能被賦值。
程序中有時(shí)會(huì)遇到if/else/return的組合,應(yīng)該將如下不良風(fēng)格的程序
if
(condition)
return
x;
return
y;
改寫為
if
(condition)
{
return
x;
}
else
{
return y;
}
或者改寫成更加簡練的
return (condition ? x : y);
4.4 循環(huán)語句的效率
C++/C循環(huán)語句中,for語句使用頻率最高,while語句其次,do語句很少用。本節(jié)重點(diǎn)論述循環(huán)體的效率。提高循環(huán)體效率的基本辦法是降低循環(huán)體的復(fù)雜性。
l
【建議4-4-1】在多重循環(huán)中,如果有可能,應(yīng)當(dāng)將最長的循環(huán)放在最內(nèi)層,最短的循環(huán)放在最外層,以減少CPU跨切循環(huán)層的次數(shù)。例如示例4-4(b)的效率比示例4-4(a)的高。
for (row=0; row<100; row++)
{
for ( col=0;
col<5; col++ )
{
sum = sum +
a[row][col];
}
}
|
for (col=0; col<5; col++ )
{
for (row=0;
row<100; row++)
{
sum = sum + a[row][col];
}
}
|
示例4-4(a) 低效率:長循環(huán)在最外層 示例4-4(b) 高效率:長循環(huán)在最內(nèi)層
l
【建議4-4-2】如果循環(huán)體內(nèi)存在邏輯判斷,并且循環(huán)次數(shù)很大,宜將邏輯判斷移到循環(huán)體的外面。示例4-4(c)的程序比示例4-4(d)多執(zhí)行了N-1次邏輯判斷。并且由于前者老要進(jìn)行邏輯判斷,打斷了循環(huán)“流水線”作業(yè),使得編譯器不能對循環(huán)進(jìn)行優(yōu)化處理,降低了效率。如果N非常大,最好采用示例4-4(d)的寫法,可以提高效率。如果N非常小,兩者效率差別并不明顯,采用示例4-4(c)的寫法比較好,因?yàn)槌绦蚋雍啙崱?span lang="EN-US">
for (i=0; i<N;
i++)
{
if (condition)
DoSomething();
else
DoOtherthing();
}
|
if (condition)
{
for (i=0; i<N; i++)
DoSomething();
}
else
{
for (i=0; i<N; i++)
DoOtherthing();
}
|
表4-4(c) 效率低但程序簡潔 表4-4(d) 效率高但程序不簡潔
4.5 for 語句的循環(huán)控制變量
l
【規(guī)則4-5-1】不可在for 循環(huán)體內(nèi)修改循環(huán)變量,防止for 循環(huán)失去控制。
l
【建議4-5-1】建議for語句的循環(huán)控制變量的取值采用“半開半閉區(qū)間”寫法。
示例4-5(a)中的x值屬于半開半閉區(qū)間“0
=< x < N”,起點(diǎn)到終點(diǎn)的間隔為N,循環(huán)次數(shù)為N。
示例4-5(b)中的x值屬于閉區(qū)間“0 =<
x <= N-1”,起點(diǎn)到終點(diǎn)的間隔為N-1,循環(huán)次數(shù)為N。
相比之下,示例4-5(a)的寫法更加直觀,盡管兩者的功能是相同的。
for (int x=0;
x<N; x++)
{
…
}
|
for (int x=0;
x<=N-1; x++)
{
…
}
|
示例4-5(a) 循環(huán)變量屬于半開半閉區(qū)間 示例4-5(b) 循環(huán)變量屬于閉區(qū)間
有了if語句為什么還要switch語句?
switch是多分支選擇語句,而if語句只有兩個(gè)分支可供選擇。雖然可以用嵌套的if語句來實(shí)現(xiàn)多分支選擇,但那樣的程序冗長難讀。這是switch語句存在的理由。
switch語句的基本格式是:
switch (variable)
{
case value1 : …
break;
case value2 : …
break;
…
default :
…
break;
}
l
【規(guī)則4-6-1】每個(gè)case語句的結(jié)尾不要忘了加break,否則將導(dǎo)致多個(gè)分支重疊(除非有意使多個(gè)分支重疊)。
l
【規(guī)則4-6-2】不要忘記最后那個(gè)default分支。即使程序真的不需要default處理,也應(yīng)該保留語句 default : break; 這樣做并非多此一舉,而是為了防止別人誤以為你忘了default處理。
自從提倡結(jié)構(gòu)化設(shè)計(jì)以來,goto就成了有爭議的語句。首先,由于goto語句可以靈活跳轉(zhuǎn),如果不加限制,它的確會(huì)破壞結(jié)構(gòu)化設(shè)計(jì)風(fēng)格。其次,goto語句經(jīng)常帶來錯(cuò)誤或隱患。它可能跳過了某些對象的構(gòu)造、變量的初始化、重要的計(jì)算等語句,例如:
goto state;
String s1, s2; // 被goto跳過
int sum = 0; //
被goto跳過
…
state:
…
如果編譯器不能發(fā)覺此類錯(cuò)誤,每用一次goto語句都可能留下隱患。
很多人建議廢除C++/C的goto語句,以絕后患。但實(shí)事求是地說,錯(cuò)誤是程序員自己造成的,不是goto的過錯(cuò)。goto 語句至少有一處可顯神通,它能從多重循環(huán)體中咻地一下子跳到外面,用不著寫很多次的break語句; 例如
{ …
{
…
{
…
goto
error;
}
}
}
error:
…
就象樓房著火了,來不及從樓梯一級一級往下走,可從窗口跳出火坑。所以我們主張少用、慎用goto語句,而不是禁用。