正確編寫C++程式碼的十大要訣(1)
《深入淺出C++(第2版)》作者原文:Top Ten Tips for Correct C++ Coding
感謝李鬆峰,部分用詞參考了正確編寫C++程式碼的10個要點(2-1)一文。
By Brian Overland @May 17, 2011
Brian Overland,曾在微軟工作十年,《深入淺出C++(第2版)》(C++ Without Fear: A Beginner's Guide That Makes You Feel Smart, 2nd Edition)一書作者,分享了自己數十年來編寫和除錯C++程式碼悟到的,10條來之不易、省時省力的要訣。
我第一次接觸C語言是在幾十年前(好吧,我知道這讓我很顯老)。後來我又學了C++。真希望那個時候有人能帶著我避開那些顯而易見的陷阱,這樣也許我可以免受不少煎熬。
至少現在我可以提供幾條這類必需的(抱歉用這個詞)指引。本文不是C++教程,而是面向C++學習者的指南。不過老實說,對於C++,總有學不完的東西。
要想編寫優美專業的C++程式碼,且容易維護,也不大需要除錯,務請牢記以下十條要訣,為此本文的要求有些比我在《深入淺出C++(第2版)》(C++ Without Fear (2/e))裡寫的還要嚴格。
這些準則不分先後(抱歉,David Letterman),不過前幾條準則更多是針對困擾初學者的錯誤。
1:不要搞混賦值(=)和相等測試(==)
這一條非常基礎,儘管可能難倒過福爾摩斯。下面這段程式碼看似正常,而且C++要是更像BASIC的話,編譯和執行也沒問題。
if (a = b)
cout << "a is equal to b.";
正因為上面的程式碼表面上看起來沒什麼問題,在大型程式裡,它引起的邏輯錯誤需要耗費數小時才能定位,除非你事先對它有所警覺。(因此我在除錯程式時第一時間會排查這類錯誤。)在C和C++裡,下面的表示式並非測試兩個變數是否相等:
a = b
這個表示式實則是將b的值賦給a,然後再求出所賦的值。
問題在於a = b的求值結果並不總是合理的真/假條件,除了我後面將提到的一大例外。但在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
cout << "a and b are not equal."; // 結果列印的卻是這條訊息!
解決辦法自然是根據需要使用相等測試。注意要用兩個等號(==),在條件裡用這個運算子才對。
// 正確版本:
if (a == b)
cout << "a and b are equal.";
2:杜絕“幻數”
所謂幻數(又稱魔數、魔術數字)是指散落於程式中未加註釋的字面值(literal number)。多數老到的程式設計師更樂見程式裡只有諸如MAX_ROWS、SCREEN_WIDTH的符號名。
總之,專業的計算機程式設計師——包括一些對數學情有獨鍾的人——其實討厭數字!
箇中緣由與歷史有點關係。遙想上世紀40年代,那時只能用各種位元位組合形成的機器碼程式設計。程式設計師生不如死,必須不停地轉換這些組合。隨後組合語言出現,程式設計用上了符號名,比之前要容易千百倍。
即使到了今天,程式設計師們也不喜歡這樣的宣告:
char input_string[81];
其中81是個“幻數”。它從哪兒來?更好的做法是用#define或enum語句限制數字的使用。
#define SCREEN_WIDTH 80
比起81,SCREEN_WIDTH更加一目瞭然,如果將來打算重新設定這個寬度,你只需修改一行程式碼。隨後,寬度修改會自動體現到如下語句:
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。這裡是用每個數字實際出現的次數除以期望出現次數N/10。比如,如果3的results(實際出現的次數)是997,而總共進行了10 000次測試,那麼就是用997除以期望出現的次數1000。
但results、n和10都是整數。結果,997除以1000得到零!
等等,我們本來預想的是0.997,到底怎麼回事?
整數除法向下舍入,得到一個最接近的整數。餘數則扔掉了。這也不一定是壞事。C++提供了兩個獨立的除法運算:除和求餘。
int dividend = n / m; // 求比例.
int remainder = n % m; // 求餘數.
對了,前面那行程式碼使用了一個“幻數”——10,我暈!不過,下面我們就來說說這個更麻煩的問題:資料丟失。
4:利用資料型別提升控制結果
在混有整數和浮點數的表示式裡,整型運算元會被提升為double型。因此,下面的表示式會產生我們想要的結果:
cout << results / (n / 10.0);
注意10.0的小數部分雖然為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++裡任意數字值都可用作條件這一規則。迴圈每執行一次n都會遞減1,直到n為0,迴圈結束。但問題在於:要是n初值為負會怎麼樣?我剛才展示的程式碼就會陷入死迴圈,或者至少一直迴圈至最小的負值,直到溢位為止。那會讓你鬱悶透頂。
總之,只有那些嚴格意義上的布林(即真/假)表示式才能用作條件。
while (n-- > 0) {
do_something();
}
不過也有一大例外。在面對操作失敗指標被置為NULL(也即0)時,這種簡寫形式很有用處。NULL實際上等同於false。空指標還可用於連結表,指示連結串列結束,節點的next_node成員指向空(nowhere,上世紀五六十年代,說的是指向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;
}
相關文章:
相關文章
- 正確編寫C++程式碼的十大要訣(2)C++
- 正確編寫C++程式碼的10個要點(2-1)C++
- 編寫高質量程式碼的十個祕訣
- 寫好程式碼十個祕訣
- 讀林斌博士寫好程式碼十個祕訣
- C++編譯錯誤的正確查詢方式C++編譯
- 讀《程式碼不朽:編寫可維護軟體的10大要則》C# 版C#
- 正確編寫Designated Initializer的幾個原則
- Java 正確的做字串編碼轉換Java字串編碼
- 編寫高質量的js之正確理解正規表示式回溯JS
- Laravel 單元測試實戰(3)- 編寫整合測試確保介面和資料庫程式碼正確Laravel資料庫
- C++編寫pingIP的程式C++
- 編寫易維護跨端元件的正確姿勢跨端元件
- 十個正確使用 Redis 的技巧Redis
- 程式設計師正確看程式碼的方式程式設計師
- 五顏六色的程式碼:論程式碼的正確位置
- 正確理解PHP程式編譯時的錯誤資訊PHP編譯
- 編寫優質無錯程式碼(1) (轉)
- 『No22: 編寫可讀程式碼的藝術(1)』
- 編寫高效且優雅的 Python 程式碼(1)Python
- Git Bash 提交程式碼的正確姿勢Git
- DDD建模後寫程式碼的正確姿勢(Java、dotnet雙平臺)Java
- 單例模式的正確寫法單例模式
- 你寫的 return null 正確嗎?Null
- java安全編碼指南之:lock和同步的正確使用Java
- 怎樣用程式碼寫出99成法口訣!
- 編寫更好的CSS程式碼CSS
- 編寫可讀的程式碼
- 優化C++程式碼(2):C++程式碼的編譯過程優化C++編譯
- JavaScript 如何正確處理 Unicode 編碼問題!JavaScriptUnicode
- JavaScript如何正確處理Unicode編碼問題!JavaScriptUnicode
- 編寫可移植C/C++程式的要點C++
- 在別人寫的程式碼上做修改我是這樣保證正確性
- 設計優秀API七大要訣API
- FB前工程主管:釋出程式碼的正確方式
- 如何正確的停止MongoDB程式MongoDB
- Sublime 編寫編譯 swift程式碼編譯Swift
- Shell文字處理編寫單行指令的訣竅