高質量C++/C編程指南[4]

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