《高質量C++/C編程指南》陷阱 【轉】


作者:幻の上帝

出處:http://hi.baidu.com/frankhb1989/item/185f0a14823dd1f8dceeca2c

此文硬傷不少,且相對譚XX的書而言隱晦許多,不建議新手學習。
主觀的論述,合理的部分,就此略過。疏漏之處也盡量忍住不吐槽。

第1章 文件結構

每個C++/C程序通常分為兩個文件。

//錯誤。沒有強調翻譯單元的概念。

另一個文件用於保存程序的實現(implementation),稱為定義(definition)文件。

//有誤。實現不簡單等同於定義。例如,類的完整聲明也是類的定義,但不是完整的實現。頭文件也可以存放其它定義(例如模板和內聯函數的實現)。


1.2
建議1-2-1

在C++語法中,類的成員函數可以在聲明的同時被定義,並且自動成為內聯函數。這雖然會帶來書寫上的方便,但卻造成了風格不一致,弊大於利。建議將成員函數的定義與聲明分開,不論該函數體有多么小。

//這條建議一般不適用於類模板。


1.4

頭文件能加強類型安全檢查。如果某個接口被實現或被使用時,其方式與頭文件中的聲明不一致,編譯器就會指出錯誤,這一簡單的規則能大大減輕程序員調試、改錯的負擔。

//在使用合理的情況下,這基本上是正確的,但頭文件可以導致其它方面——像重構(典型情況如重命名一個函數)的復雜化。


第2章 程序的版式
這章基本是主觀內容。讀者需要注意風格是有爭議的,其中的“良好風格”或“不良風格”並非公認。

2.8
很多C++教課書受到Biarne Stroustrup第一本著作的影響,不知不覺地采用了“以數據為中心”的書寫方式,並不見得有多少道理。

我建議讀者采用“以行為為中心”的書寫方式,即首先考慮類應該提供什么樣的函數。這是很多人的經驗——“這樣做不僅讓自己在設計類時思路清晰,而且方便別人閱讀。因為用戶最關心的是接口,誰願意先看到一堆私有數據成員!”

//“ Biarne Stroustrup ”拼寫錯誤。C++之父的名字是 Bjarne Stroustrup 。有些一廂情願了: C++ 的類定義中既然允許顯式地表示 private 成員,就不是純粹的接口描述方式。以我個人的經驗,數據成員寫在后面可能會導致順序閱讀代碼時需要回溯。當類定義長度較小時這點影響不大,但定義長度比較長的時候(例如Loki::Functor里的代碼)就會需要較頻繁地滾屏,影響代碼閱讀效率。


第3章 命名規則
這章也基本是主觀內容。

3.1
規則3-1-1

標識符應當直觀且可以拼讀,可望文知意,不必進行“解碼”。

//這點和此文建議使用的匈牙利命名法有一定程度的矛盾。


規則3-1-3

命名規則盡量與所采用的操作系統或開發工具的風格保持一致。

//盡管大部分人應該樂於支持這個觀點,不過事實上有時候無法實現。例如同時使用標准庫和Windows API 風格的代碼。這時倒不妨直接約定允許根據上下文選擇要使用的命名風格。要點是,應該讓人看出某個名稱是用哪個風格命名的,而不至於一眼就混淆來源。


3.2
規則3-2-7

為了防止某一軟件庫中的一些標識符和其它軟件庫中的沖突,可以為各種標識符加上能反映軟件性質的前綴。例如三維圖形標准OpenGL的所有庫函數均以gl開頭,所有常量(或宏定義)均以GL開頭。

//在 C++ 中應該考慮是否可以用命名空間代替前綴。


第4章 表達式和基本語句

我真的發覺很多程序員用隱含錯誤的方式寫表達式和基本語句,我自己也犯過類似的錯誤。

//作者似乎沒搞清楚“錯誤”一詞的語義。


4.3
規則4-3-4
不要寫成
        if (p== 0)  // 容易讓人誤解p是整型變量
        if (p!= 0)

//事實上,C++中的NULL典型地就是 int 字面量 0 (考慮到成文時間,不提新標准的空指針類型),和 int 兼容。以 Bjarne Stroustrup 的觀點,這樣恰恰會使人誤以為 NULL 不是整數,因此推薦用 0 而不是 NULL 。


4.3.5

或者改寫成更加簡練的return (condition ? x : y);

//這里的括號是多余的。


4.4
這節不是語言本身而是涉及語言實現的內容。以現在的觀點來看,優化器會可能會在此做一些工作。當然了解一些相關原理大體上還是有益的。

第5章 常量

常量是一種標識符,它的值在運行期間恆定不變。C語言用 #define來定義常量(稱為宏常量)。C++ 語言除了 #define外還可以用const來定義常量(稱為const常量)。

//譚XX風格的信口開河。這段引文中逗號或者句號之間的內容,沒一個能算得上是正確的。


5.2
規則5-2-1

在C++程序中只使用 const常量而不使用宏常量,即const常量完全取代宏常量。

//這是有問題的。事實上很多情況下 const 只能讓編譯器被修飾的對象當做只讀變量,而非編譯期的真正意義的常量進行處理。與 #define 的符號常量(字面量)相比,只讀變量受到了一些限制,例如不能作 case 的標號。


第6章 函數設計

 C語言中,函數的參數和返回值的傳遞方式有兩種:值傳遞(pass by value)和指針傳遞(pass by pointer)。

//錯誤。形式上, C 語言函數參數只按值傳遞。所謂的指針傳遞是按值傳遞的一種,只是傳遞參數的類型是指針而已。


6.1
規則6-1-1

參數的書寫要完整,不要貪圖省事只寫參數的類型而省略參數名字。如果函數沒有參數,則用void填充。

//不妥當。有時候參數的名稱並非有意義,像 int max(int, int); 之類的原型,寫了也不會讓函數的意義更清楚。此外,並沒有指出C函數沒有參數時參數列表為 void ,這和省略參數列表(接受任意參數)是不同的。而 C++ 中省略參數列表和 void 參數相同,參數列表 ... 接受不確定個數的參數。


6.2
規則6-2-3

不要將正常值和錯誤標志混在一起返回。正常值用輸出參數獲得,而錯誤標志用return語句返回。

//在C++中可以不使用此規則而使用異常(在 C 中理論上也可以類似地使用 setjmp/longjmp ,但容易造成語義不明確,實際上基本不用)。


6.3
規則6-3-1

在函數體的“入口處”,對參數的有效性進行檢查。

//在函數接口語義明確的情況下並非是必需的。例如 C 標准庫 <string.h> 中以及 POSIX 標准中的許多函數。

規則6-3-2

在函數體的“出口處”,對return語句的正確性和效率進行檢查。

//同樣不是必需的。此外檢查可能損失效率。

//這里是誤讀。但原文關於“臨時變量”的說法有誤。

要搞清楚返回的究竟是“值”、“指針”還是“引用”。

//注意返回對象的語義,但是刻意區分“值”和“指針”是不必要的,嚴格上是錯誤的——它們根本就不是可以比較的一類概念。

建議6-4-2

函數體的規模要小,盡量控制在50行代碼之內。

//盡管為數不多,有些特殊情況,如編譯器的某些分析程序,是明顯的反例。


第7章 內存管理
“640Kought to be enough for everybody

—Bill Gates 1981”

//和本章主題無關。


7.1

內存分配方式有三種

//有誤。內存分配具體方式由實現決定,語言只限制存儲類。


 7.2
漏了重復釋放內存的錯誤(這通常會引起程序崩潰)。
規則7-2-1

用malloc或new申請內存之后,應該立即檢查指針值是否為NULL。防止使用指針值為NULL的內存。

//對 C++ 而言是錯誤的。 ISO C++ 關於內存分配失敗的默認行為是拋出 std::bad_alloc 異常。如果要使分配失敗不拋出異常,使用 nothrow 版本,或者設置實現相關的編譯選項。

規則7-2-5

用free或delete釋放了內存之后,立即將指針設置為NULL,防止產生“野指針”。

//不一定必要。例如指針是自動變量,在退出所在的塊作用域被自動釋放時。


7.3.1

該語句企圖修改常量字符串的內容而導致運行錯誤

//有誤。 C 語言中字符串字面量(具有數組類型)未必是常量。當然還是應該避免修改字符串字面量,這是未定義行為。


7.9

為new和malloc設置異常處理函數。例如VisualC++可以用_set_new_hander函數為new設置用戶自己定義的異常處理函數,也可以讓malloc享用與new相同的異常處理函數。

//應該補充的是,標准庫有 std::set_new_handler 。


第8章C++函數的高級特性

const與virtual機制僅用於類的成員函數。

//應該強調“非靜態”成員函數,否則就是錯誤的。


(雖然似乎沒有更多明顯的錯誤,不過遺漏的地方一堆,堅決不吐槽……= =)

第9章 類的構造函數、析構函數與賦值函數
9.1

Stroustrup的命名方法既簡單又合理:讓構造函數、析構函數與類同名,由於析構函數的目的與構造函數的相反,就加前綴‘~’以示區別。

//錯誤。構造函數和析構函數是無名的(這個細節決定了一些語言特性的限制,例如不能 using 聲明基類的構造函數),看起來名稱和類名相同的調用方式其實是語法的限制。

非內部數據類型的成員對象應當采用第一種方式初始化,以獲取更高的效率。

//效率在這里倒是次要的(很容易被編譯器優化掉),重要的是語義。另外,初始化列表的一些行為是受到特殊限制的,例如基類子對象和成員的初始化順序和異常。

9.7

(仍然不想吐槽漏掉swap&copy這樣高效可靠的方法而在operator=實現里分配內存……)


第10章 類的繼承與組合
首先標題有點問題。類可以繼承,組合的應該是類的實例而非本身。

如果將對象比作房子,那么類就是房子的設計圖紙。所以面向對象設計的重點是類的設計,而不是對象的設計。

//因果關系不成立。而且觀點是有問題的,僅適於經典的 class-based OOD 。


10.2
規則10-2-1

若在邏輯上A是B的“一部分”(a partof),則不允許B從A派生,而是要用A和其它東西組合出B。

//錯誤。盡管一般組合能夠勝任這項工作,但並非絕對。可以使用private繼承完成相同的任務,並且提供覆蓋成員函數的特性。這是組合無法完成的。


第11章 其它編程經驗
11.1.3

如果在編寫const成員函數時,不慎修改了數據成員,或者調用了其它非const成員函數,編譯器將指出錯誤

//錯誤漏了 mutable 修飾的成員這個特例。

建議11-3-8

避免編寫技巧性很高代碼。

//不應該逃避。技巧性高不意味着難以理解;即使難以理解,也可以通過注釋彌補。當然,前提是技巧要使用得合理。

建議11-3-13

把編譯器的選擇項設置為最嚴格狀態。

//有時候現實不允許,例如考慮已有代碼的兼容性,需要配置時會帶來額外的復雜性。


附錄C :C++/C試題的答案與評分標准
標准答案示例:
const float EPSINON = 0.00001;

if ((x >= - EPSINON) && (x <= EPSINON)

//如果是“標准”的,為什么不用標准庫的 <float.h>/<cfloat> 的FLT_EPSILON而自己重復發明輪子?

 

三、簡答題(25分)

1、頭文件中的ifndef/define/endif 干什么用?(5分)

答:防止該頭文件被重復引用。

//錯誤。條件編譯顯然不只用於給頭文件增加 header guard 。


四、有關內存的思考題(每小題5分,共20分)
答:程序崩潰。
因為GetMemory並不能傳遞動態內存,
Test函數中的 str一直都是 NULL。

strcpy(str, "hello world");將使程序崩潰。

//錯誤。未定義行為不等同於程序崩潰,盡管很可能是這樣。


六、編寫類String的構造函數、析構函數和賦值函數(25分)

String& String::operate =(const String &other)

//關鍵字operator拼寫錯誤。實現冗余就不說了。

EOF


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM