正確編寫C++程式碼的10個要點(2-1)

李鬆峰發表於2011-11-02

《深入淺出C++(第2版)》作者的原文:Top Ten Tips for Correct C++ Coding

1:不要搞混賦值(=)與相等測試(==)

儘管可能會難住福爾摩斯,但這一條確實太初級了。假如C++再像一點BASIC,那麼下面的語句應該是無害的,而且可以正常編譯執行:

if (a = b)
    cout << "a is equal b.";

由於表面上看不出有什麼問題,因此這種寫法在大程式中就會造成難以追蹤的邏輯錯誤——除非你有意尋找。(我在除錯程式時,首先會查這種問題。)在C和C++中,下面的表示式並非在測試兩個變數是否相等:

a = b

當然,這個表示式的意思是把b的值賦給a,然後再求出所賦的值。

問題在於,對a = b求值的結果通常得不到合理的true/false條件——稍後我會提到一個明顯的例外。但在C和C++中,任何數值都可以用作if或while語句的條件。

假設a和b的值都是0。前面那條if語句的作用就是把b的值放到a中,然後對錶達式a = b求值得到0,而0等同於false。結果,a和b本來相等,但列印出來的訊息卻錯誤的:

if (a = b) //這個條件本來是用於確保a和b相等的
    cout << "a and b are equal.";
else
    coun << "a and b are not equal."; //但列印出來的是這個訊息

解決方案當然是有意識地使用相等測試。注意相等測試使用兩個等號,這樣的條件才是正確的:

//正確的寫法
if (a == b)
    cout << "a and b are equal.";

2:別用“魔法數字”

所謂魔法數字,指的是在程式裡突然出現一個數字,但沒有任何解釋。大多數老到的程式設計師都願意在程式裡看到像MAX_ROWS、SCREEN_WIDTH這樣有意義的符號名。

簡言之,專業的計算機程式設計師——包括一些對數學情有獨鍾的人,都很討厭數字。

瞭解一點歷史有助理解為什麼。那是在1940年代,所有人程式設計使用的都是機器碼,程式全都是位模式。生活在十八層地獄第十八層的程式設計師必須不斷地翻譯這些模式。而當組合語言中引入了符號名之後,程式設計一下子簡單了一千倍。

即使是到了今天,程式設計師們也不喜歡類似這樣的宣告:

char input_string[81];

這裡的81就是一個“魔法數字”。這個數是從哪兒來的?更好的做法是使用#define或enum語句來控制數字的使用。

#define SCREEN_WIDTH 80

SCREEN_WIDTH比81更好理解,如果你想將來重置這個寬度,只要修改一行程式碼即可。而且修改之後就能夠自動地反映到類似下面這樣的語句當中:

int input_string[SCREEN_WIDTH + 1];

3:別太依賴整數除法(除非你就想那麼做)

在不想儲存分數的情況下,整型(在C/C++語系中是int,以及short、long和現在的long long)是比較好的選擇———但我們這裡不談小數。

不過,有時候整數在位於一個長表示式中時也會牽扯到小數。下面就是我的書《深入淺出C++(第2版)》(C++ Without Fear)中的一行程式碼:

cout << results / (n / 10);

這個程式用於生成0到9之間的隨機數,每個數字出現的概率應該是1/10。這裡是用每個數字實際出現的次數除以期望它出現的次數。比如,如果3的results(出現的實際次數)是997,而總共進行了10 000次測試,那麼就是用997除以期望它出現的次數:1000。

此時的results、n和10都是整數。因此,997除以1000後得到的是0!

……等等,我們覺得應該得0.997啊。怎麼回事?

整數除法向下舍入,於是會得到一個完美的整數。餘數呢?扔了。好像也不是完全沒有道理。C++提供了兩種 獨立的除法操作:除和求餘。

int dividend = n / m;   //儲存比例
int remainder = n % m;  //儲存餘數

噢——對了,前面那行程式碼中使用了一個“魔法數字”——10,我暈~$#@&^!好了,下一條要訣我們就聊聊這個更麻煩的事兒:資料丟失。

4:記住利用資料提升來控制結果

在混有整數和浮點數的表示式中,整數運算數會被提升為double型。因此,下面的表示式可以得到我們想要的結果:

cout << results / (n / 10.0);

注意,雖然並沒有小數,但這裡用了10.0,就是為了把它儲存為double型。這樣,C++就會把n和results也提升為double型,最終達到執行浮點除法的目的:

另一種達到目的的方法是轉型資料:

cout << results / (n / (double) 10);
cout << results / (n / static_cast<double>(10));

5:不要使用非布林條件(除非你會留意)

C語言最初是為了編寫作業系統而設計的,因而它賦予了程式設計師很大的自由,不僅可以在機器碼的層面上運算元據(通過使用指標),還可以寫出快捷方法。快捷方法對於初學者是很危險的,但對於知道該怎麼用的程式設計師來說,有時候則非常可心。但願他們心甘情願活在危險之中。

比如,下面是“做N次……”的一種最優雅的快捷寫法:

while (n--){
    do_something();
}

實際上還可以寫得更短:

while (n--) do_something();

同樣,無論怎樣寫,都是基於一條相同的規則:C和C++中的任何數值都可以作為條件來用。當每次迴圈遞減1,每次迴圈遞減1,直到n變成0時,迴圈結束。不過,這裡有問題:如果n一開始就是負值呢?那剛才看到的那個迴圈就會永遠迴圈下去,至少是可以迴圈到記憶體溢位之前的最小負值。這會破壞你的好心情的。

一般來說,只有嚴格意義上的Boolean(也就是true或false)表示式才能用作條件:

while (n-- > 0){
    do_something();
}

不過也有一個例外。當某個操作失敗時,將指標設定為NULL(也就是0),那這個簡寫方式就能起作用。實際上,NULL意味著false。在連結串列結構中,空指標也可以用來表示列表的結束,就是讓它的next_node成員指向不存在的地方(nowhere;1950年和1960年的時候,說是指向Nowheresville)。在下面這種情況下,空指標意味著開啟檔案失敗:

if (! file_pointer){
    cout << "File open failed.";
    return ERROR_CODE;
}

提醒一句,你應該在這個條件中使用一個賦值語句:

if (! (file_pointer = open_the_file(args)){
    cout << "File open failed.";
    return ERROR_CODE;
}

(To be continued!)

本文參加 Translate Geeks to Chinese 翻譯活動

相關文章