Effective C++(中文版)下载

来源:互联网 发布:employees数据库 编辑:程序博客网 时间:2024/05/17 07:13

Effective C++(中文版)下载

《Effective C++(中文版)》




导读:
學會一個程式語言,是一回事兒;學會如何以此語言設計並實作出有效的程式,又是一回事兒。C++ 尤其如此,因為它很不尋常地涵蓋了罕見的威力和豐富的表現力,不但建立在一個全功能的傳統語言(C)之上,更提供極為廣泛的物件導向(object-oriented)性質,以及對templates 和exceptions(異常狀態)的支援。
假以適當運用,C++ 是個可以讓你感受愉悅的夥伴。各種不同的設計方式,包括物件導向型式和傳統型式,都可以直接在這個語言中表現並有效地實作出來。你可以定義新的資料型別,它們和語言內建的型別表面上無分軒輊,實質上則更具彈性。明智地選用一些謹慎設計的classes — 自動完成記憶體管理、別名(aliasing)處理、初始化動作與清理動作、型別轉換、以及軟體開發的其他難題與禍根— 可以使程式設計更容易,更直觀,更有效,更少錯誤。是的,要寫出有效的C++ 程式並不會太困難,如果你知道怎麼做的話。
如果沒有什麼訓練與素養,就冒然使用C++,會導至做出來的碼不易理解、不易維護、不易擴充、缺乏效率、而且容易出錯。
關鍵在於找出C++ 可能絆倒你的狀況有哪些,然後學習如何避開它們。這正是本書的目的。我假設你已經認識C++ 並對它有某種程度的使用經驗。我提供一些準則,讓你更有效地使用這個語言,使你的軟體容易理解、容易維護、容易擴充、效率高、而且行為如所預期。
我提出的忠告分為兩大類:一般性的設計策略,以及特殊的(比較難得一見的)語言性質。
設計方面的討論集中在如何對不同的方法(俾得以C++ 達成某個目標)做取捨。如何在inheritance(繼承)和templates(範本)之間做選擇?在templates 和generic pointers(泛型指標)之間?在public inheritance(公開繼承)和private inheritance (私有繼承)之間?在private inheritance 和layering(分層技術)之間?在function overloading(函式多載化)和parameter defaulting(參數預設值)之間?在virtual function(虛擬函式)和nonvirtual functions(非虛擬函式)之間?在pass-by-value (傳值)和pass-by-reference(傳址)之間?一開始就做出正確的決定是很重要的,因為不正確的選擇或許不會一下子就浮現影響,但是在開發過程的後期,矯正它往往很困難、很花時間,很混亂,很令人沮喪,事倍功半,成本很高。
在你確切知道你要做什麼之後,把它做對,恐怕也不是件太容易的事。什麼是 assignment 運算子的適當傳回型別?當operator new 無法找出足夠的記憶體,它該有怎樣的行為?destructor 何時應該被宣告為virtual?你應該寫一個member initialization list(成員初值列)嗎?在如斯細節中努力,也頗具有決定性,因為如果不這樣,常會導至意料之外或神秘難解的程式行為。更糟的是這類脫軌行為可能不會立即浮現,這些恐怖的碼或能通過品管檢驗,卻仍然藏匿著許多未偵測出來的臭蟲— 不定時炸彈正等待引爆。
這不是本得一頁頁讀下去才有感覺的書籍。你甚至不需要依序讀它。所有素材被我分為50 個條款,每一個都相當獨立。不過條款之間會彼此參考,所以閱讀本書的一種方法是先從感興趣的條款開始,然後遵循其參考指示,進一步讀下去。
所有條款被我分為七大類。如果你對某類主題特別感興趣,例如「記憶體管理」或「物件導向設計」,可以從相關章節開始,一路讀下去,或是跳躍前進。不過最後你會發現,本書的所有內容對於高實效的C++ 程式設計而言,都十分基礎而重要,所以幾乎每個條款最後都會和其他條款互有牽連。
這並不是一本C++ 參考工具書,也不是一本讓你從頭學習C++ 的書。例如,雖然我熱切告訴你一些有關「撰寫自己的operator new」的注意事項(條款7~10),但是我假設你可以從其他地方獲知,operator new 必須傳回一個void*,其第一引數的型別必須是size_t。許多C++ 語言書可以帶給你這樣的資訊。
這本書的目的是要強調那些其他書籍往往淺淺帶過(如果有的話)的C++ 程式設計概念。其他書籍描述的是C++ 語言的各個成份,本書則告訴你如何將那些成份組合起來,完成一個有效的程式。其他書籍告訴你如何讓程式順利編譯,本書則告訴你如何避開編譯器不會告訴你的一些問題。
和大部份語言一樣,C++ 有著豐富的「傳統」,在程式員之間口耳相傳,形成這個語言的偉大傳承的一部份。我企圖在這本書中以容易閱讀的型式記錄一些長久累積而來的智慧。
然而在此同時,我必須告訴你,本書僅限於正統的、可移植的C++ 語言。只有明列於ISO/ANSI 標準(見條款M35)中的性質,才會被本書採用。本書之中,移植性是個關鍵考量。如果你想要尋找因編譯器而異的特殊技法,本書不適合你。
但是,啊呀,標準規格所描述的C++,與社區軟體商店所賣的編譯器(s) 的表現,多少有點出入。所以當我指出某個新的語言特性頗有用處時,我也會告訴你如何在缺乏那些特性的情況下產出有效的軟體。畢竟在確知未來即將如何如何之際,卻忽略那些美麗遠景而儘做些低下的勞力工作,容我坦言是相當愚蠢的;但是反過來看,你也不能在最新最偉大的C++ 編譯器(s) 降臨世界之前,空自等待而束手無策呀。你必須和你手上可用的工具一起打拼,而本書正打算幫助你這麼做。
注意我說編譯器(s) — 複數。不同的編譯器對標準C++ 的滿足程度各不相同,所以我鼓勵你至少以兩種編譯器(s) 來開發程式。這麼做可以幫助你避免不經意仰賴某個編譯器專屬的語言延伸性質,或是誤用某個編譯器對標準規格的錯誤闡示。這也可以幫助你避免使用過度先進的編譯器特殊技術,例如獨家廠商才做得出來的某種語言新特性。如此特性往往實作不夠精良(臭蟲多,要不就是表現遲緩,或兩者兼具),而且C++ 社群往往對這些特性缺乏使用經驗,無法給你應用上的忠告。雷霆萬鈞之勢固然令人興奮,但當你的目標是要產出可靠的碼,恐怕還是步步為營(並且能夠與人合作)得好。
你在本書中找不到C++ 的必殺秘笈,也看不到通往C++ 完美軟體的唯一真理。 50 個條款中的每一個帶給你的都只是準則,包括如何完成較好的設計,如何避免常見的問題,如何到達更好的效率,但任何條款都不可能放之四海皆準。軟體的定義和實作是極為複雜的工作,常會受到硬體、作業系統、以及應用軟體的束縛,所以我能夠做的最好事情就是提供一些準則,讓你可以依循產生出比較好的程式。
如果任何時候你都奉行每一個條款,應該不太可能掉進最常見的一些C++ 陷阱。不過準則畢竟只是準則,可能存在例外情況。那正是為什麼每個條款都帶有一堆解釋的原因。這些解釋是本書最重要的資產。唯有徹底瞭解一個條款背後的基本原理,你才能合理決定此條款是否適用於手上的專案,或你正艱苦奮鬥的難題上。
本書的最佳用途,就是增進你對C++ 行為的瞭解,知道它為什麼有那樣的表現,以及如何將其行為轉化為你的利益。盲目運用本書所列的條款並不適當,不過話說回來,你或許不應該在缺乏好理由的情況任意違反任何一個條款。
這樣性質的書籍中,專用術語的解釋並非重點所在。那樣的工作頂好是留給語言界的「律師」去做。然而有少量C++ 辭彙是每個人都應該要懂的。以下術語一再出現,所以有必要確定你我之間對它們有共同的認知。
所謂宣告(declaration),用來將一個object、function、class 或template 的型別名稱告訴編譯器。宣告式並不帶有細目資訊。下面統統都是宣告:

[pre]extern int x; // object declaration
int numDigits(int number); // function declaration
class Clock; // class declaration
template
class SmartPointer; // template declaration
[/pre]所謂定義(definition[/pre]),用來將細目資訊提供給編譯器。對object 而言,其定義式是編譯器為它配置記憶體的地點。對function 或function template 而言,其定義式提供函式本體(function body)。對class 或class template 而言,其定義式必須列出該class 或template 的所有members:

[pre]int x; // 這是物件的定義式int numDigits(int number) // 這是函式的定義式{ // 此函式傳回其參數的數位(digits)個數int digitsSoFar = 1;if (number < 0) {number = -number;++digitsSoFar;}while (number /= 10) ++digitsSoFar;return digitsSoFar;}class Clock { // 這是class 的定義式public:Clock();~Clock();int hour() const;int minute() const;int second() const;...};templateclass SmartPointer { // 這是template 的定義式public:SmartPointer(T *p = 0);~SmartPointer();T * operator->() const;T& operator*() const;...};[/pre]上述程式碼把我們帶往所謂的constructorsdefault constructor 意指可以「不需任何引數就被喚起」者。這樣的一個constructor 如果不是沒有任何參數,就是每個參數都有預設值。通常當你需要定義物件陣列時,就會需要一個default constructor[/pre]:

[pre]class A {public:A(); // default constructor};A arrayA[10]; // 呼叫constructors 10 次class B {public:B(int x = 0); // default constructor};B arrayB[10]; // 呼叫constructors 10 次,// 每次都給引數0。class C {public:C(int x); // 這不是一個default constructor};C arrayC[10]; // 錯誤![/pre]或許有時候你會發現,某個class 的default constructor 有預設參數值,你的編譯器卻拒不接受其物件陣列。例如某些編譯器拒絕接受上述arrayB 的定義,即使它其實符合C++ 標準。這是存在於C++ 標準規格書和實際編譯器行為之間的一個矛盾例子。截至目前我所知道的每一個編譯器,都有一些這類不相容缺點。在編譯器廠商追上C++ 語言標準之前,請保持你的彈性,並安慰自己,也許不久後的某一天,C++ 編譯器的表現就可以和C++ 標準規格書所描述的一致了。
附帶一提,如果你想要產生一個物件陣列,但該物件型別沒有提供default constructor[/pre],通常的作法是定義一個指標陣列取而代之,然後利用new 一一將每個指標初始化:

[pre]C *ptrArray[10]; // 沒有呼叫任何constructorsptrArray[0] = new C(22); // 配置並建構一個C 物件ptrArray[1] = new C(4); // 同上...[/pre]這個作法在任何場合幾乎都夠用了。如果不夠,你或許得使用[url=mk:@MSITStore:E:/资料/c++/《Effective%20C++(中文版)》.chm::/file/ch04a.htm]條款14[/url] 所說的更高層次(也因此更不為人知)的"placement new" 方法。回到術語來。所謂copy constructor[/pre] 係以某物件做為另一同型物件的初值:

[pre]class String {public:String(); // default constructorString(const String& rhs); // copy constructor...private:char *data;};String s1; // 呼叫default constructorString s2(s1); // 呼叫copy constructorString s3 = s2; // 呼叫copy constructor[/pre]或許copy constructor[/pre] 最重要的用途就是用來定義何謂「以by value 方式傳遞和傳回物件」。例如,考慮以下效率不佳的作法,以一個函式串接兩個String 物件:

[pre]const String operator+(String s1, String s2){String temp;delete [] temp.data;temp.data =new char[strlen(s1.data) + strlen(s2.data) + 1];strcpy(temp.data, s1.data);strcat(temp.data, s2.data);return temp;}String a("Hello");String b(" world");String c = a + b; // c = String("Hello world")[/pre]其中operator+ 需要兩個String 物件做為參數,並傳回一個String 物件做為運算結果。不論參數或運算結果都是以by value 方式傳遞,所以在operator+進行過程中,會有一個copy constructor 被喚起,用以將a 當做s1 的初值,再有一個copy constructor 被喚起,用以將b 當做s2 的初值,再有一個copyconstructor 被喚起,用以將temp 當做c 的初值。事實上,只要編譯器決定產生中介的暫時性物件,就會需要一些copy constructor 呼叫動作(見條款M19)。重點是:pass-by-value 便是「呼叫copy constructor」的同義詞。
順帶一提,你不能夠真的像上述那樣實作Strings 的operator+。傳回一個const String object 是正確的(見[url=mk:@MSITStore:E:/资料/c++/《Effective%20C++(中文版)》.chm::/file/ch05d.htm]條款21[/url] 和[url=mk:@MSITStore:E:/资料/c++/《Effective%20C++(中文版)》.chm::/file/ch06a.htm]23[/url]),但是你應該以by reference 方式(見[url=mk:@MSITStore:E:/资料/c++/《Effective%20C++(中文版)》.chm::/file/ch05e.htm]條款22[/url])傳遞那兩個參數。
其實,如果你有外援,並不需要為Strings 撰寫operator+。事實上你的確有外援,因為C++ 標準程式庫([url=mk:@MSITStore:E:/资料/c++/《Effective%20C++(中文版)》.chm::/file/ch12e.htm]條款49[/url])就內含有一個string 型別,帶有一個 operator+,做的事情幾乎就是上述operator+ 的行為。本書中我並用String 和string 兩者(注意前者名稱以大寫開頭,後者否),但方式不同。如果我只是需要一般字串,不在意它是怎麼做出來的,那麼我便使用標準程式庫提供的 string。這也是你應該選擇的行為。然而如果我打算剖析C++ 的行為,並因而需要某些實作碼來示範或驗證,我便使用非標準的那個String class。身為一個程式員,只要必須用到字串,就應該儘可能使用標準的string 型別;那種「開發自己的字串類別,以象徵具備C++ 某種成熟功力」的日子已經過去了(不過你還是有必要瞭解開發一個像string 那樣的classes 所需知道的課題)。對「示範或驗證」目的(而且可說只對此種目的)而言,String 很是方便。無論如何,除非你有很好的理由,否則都不應該再使用舊式的char*-based 字串。具有良好定義的string 型別如今已能夠在每一方面比char*s 更具優勢,並且更好— 包括其執行效率(見[url=mk:@MSITStore:E:/资料/c++/《Effective%20C++(中文版)》.chm::/file/ch12e.htm]條款49[/url]和條款M29~M30)。
接下來兩個需要掌握的術語是initialization(初始化)和assignment(指派)。物件的初始化行為發生在它初次獲得一個值的時候。對於「帶有constructors」之 classes 或structs,初始化總是經由喚起某個constructor 達成。這和物件的 assignment[/pre] 動作不同,後者發生於「已初始化之物件被指派新值」的時候:

[pre]string s1; // initialization(初始化)string s2("Hello"); // initialization(初始化)string s3 = s2; // initialization(初始化)s1 = s3; // assignment(指派)[/pre]純粹從操作觀點看,initializationassignment 之間的差異在於前者由constructor 執行,後者由operator= 執行。換句話說這兩個動作對應不同的函式動作。
C++ 嚴格區分此二者,原因是上述兩個函式所考慮的事情不同。Constructors 通常必須檢驗其引數的有效性(validity),而大部份assignment 運算子不必如此,因為其引數必然是合法的(因為已被建構完成)。另一方面,assignment 動作的標的物並非是尚未建構完成的物件,而是可能已經擁有配置得來的資源。在新資源可被指派過去之前,舊資源通常必須先行釋放。這裡所謂的資源通常是指記憶體。在assignment 運算子為一個新值配置記憶體之前,必須先釋放舊值的記憶體。
下面是String 的constructorassignment[/pre] 運算子的可能作法:

[pre]// 以下是一個可能的String constructorString::String(const char *value) {{if (value) { // 如果指標value 不是nulldata = new char[strlen(value) + 1];strcpy(data,value);}else { // 處理null 指標    //此一「接受一個const char* 引數」的String constructor,    //有能力處理傳進來的指標為null的情況。標準的string 可沒如此寬容。    //企圖以一個null 指標產生一個string,其結果未有定義。    //不過以一個空的char*-based 字串(例如"")產生一個string 物件,    //倒是安全的。data = new char[1];}}// 以下是一個可能的String assignment 運算子String& String::operator=(const String& rhs){if (this == &rhs)return *this; // 見[url=mk:@MSITStore:E:/资料/c++/《Effective%20C++(中文版)》.chm::/file/ch04d.htm]條款17[/url]delete [] data; // 刪除(釋放)舊有的記憶體data = // 配置新的記憶體new char[strlen(rhs.data) + 1];strcpy(data, rhs.data);return *this; // 見[url=mk:@MSITStore:E:/资料/c++/《Effective%20C++(中文版)》.chm::/file/ch04b.htm]條款15[/url]}[/pre]注意,constructor 必須檢驗其參數的有效性,並確保member data 都被適當地初始化,例如一個char* 指標必須被適當地加上null 結束字元。亦請注意 assignment 運算子認定其參數是合法的,反倒是它會偵測諸如「自己指派給自己」這樣的病態情況(見[url=mk:@MSITStore:E:/资料/c++/《Effective%20C++(中文版)》.chm::/file/ch04d.htm]條款17[/url]),或是集中心力確保「配置新記憶體之前先釋放舊有記憶體」。這兩個函式的差異,象徵物件初始化(initialization)和物件指派(assignment)兩者的差異。順帶一提,如果delete [] 這樣的表示法對你而言很陌生,[url=mk:@MSITStore:E:/资料/c++/《Effective%20C++(中文版)》.chm::/file/ch01d.htm]條款5[/url] 和條款M8 應該能夠消除你的任何相關疑惑。
我要討論的最後一個術語是client[/pre](客戶)。Client 代表任何「使用你所寫的碼」的人。當我在本書提及clients,我指的便是任何觀察你的碼並企圖理解它們的人。我也是指閱讀你的class 定義並企圖決定是否可以繼承它們的人。我同時也是指那些審查你的設計並希望洞察其中原理的人。你或許還不習慣去想到你的clients,但是我會儘量說服你設法讓他們的生活愉快一些。畢竟,你也是他人所開發的軟體的client,難道你不希望那些人讓你的生活愉快一些嗎?此外,也許有一天你會發現你必須使用自己所寫的碼(譯註:指那些classes 或libraries),那時候你的client 就是你自己。
我在本書用了兩個你可能不甚熟悉的C++ 性質,它們都是晚近才加入C++ 標準之中。第一個是bool 型別,其值若非true 就是false(兩者都是關鍵字)。語言內建的相對關係運算子(如<, >, ==)的傳回型別都是bool,if, for, while, do 等述句的條件判斷式的傳回型別也是bool。如果你的編譯器尚未實作出bool 型別,你可以利用typedef 模擬bool,再以兩個const 物件模擬true 和false:

[pre]typedef int bool;const bool false = 0;const bool true = 1;[/pre]這種手法相容於傳統的C/C++ 語意。使用這種模擬作法的程式,在移植到一個支援bool 型別的編譯器平台後,行為並不會改變。如果你想知道另一種bool 模擬法,包括其優缺點討論,請參考More Effective C++ 的導讀部份。
第二個新特性其實有四樣東西,分別是static_cast, const_cast, dynamic_cast, reinterpret_cast 四個轉型運算子。傳統的C 轉型動作如下:

[pre](type) expression // 將expression 轉為type 型別[/pre]新的轉型動作則是這樣:

[pre]static_cast(expression) // 將expression 轉為type 型別const_cast(expression)dynamic_cast(expression)reinterpret_cast(expression)[/pre]這些不同的轉型運算子有不同的作用:
const_cast用來將物件或指標的常數性(constness)轉型掉,我將在[url=mk:@MSITStore:E:/资料/c++/《Effective%20C++(中文版)》.chm::/file/ch05d.htm]條款21[/url]驗證這個主題。dynamic_cast用來執行「安全的向下轉型動作(safe downcasting)」,這是[url=mk:@MSITStore:E:/资料/c++/《Effective%20C++(中文版)》.chm::/file/ch10c.htm]條款39[/url] 的主題。reinterpret_cast的轉型結果取決於編譯器— 例如在函式指標型別之間做轉型動作。你大概不常需要用到reinterpret_cast。本書完全沒有用到它。static_cast是個「雜物袋」:沒有其他適當的轉型運算子可用時,就用這個。它最接近傳統的C 轉型動作。

傳統的C 轉型動作仍然合法,但是新的轉型運算子比較受歡迎。它們更容易在程式碼中被識別出來(不論是對人類或是對諸如grep 等工具而言),而且愈是縮小範圍地指定各種轉型運算子的目標,編譯器愈有可能診斷出錯誤的運用。例如,只有const_cast 才可以用來將某物的常數性(constness)轉換掉。如果你嘗試使用其他轉型運算子來轉換物件或指標的常數性,一定會踢到鐵板。
欲知這些新式轉型動作的更多資訊,請看條款M2,或查閱較新的C++ 語言書籍。 M 代表More Effective C++,是我的另一本書。本書最後附有一份該書摘要。
本書的程式範例中,我設法為objects, classes, functions 取一些有意義的名稱。許多書籍在選用識別名稱時,都喜歡恪守一句箴言:簡短是智慧的靈魂,但是我不,我喜歡一切都交待得清清楚楚。我努力打破傳統,堅不使用那種隱秘而不易為人識破天機的名稱。但偶爾我會被誘惑所屈服,使用兩個我最歡迎的參數名稱。其意義可能並不淺顯易懂,特別是如果你從未在任何編譯器開發團隊待過的話。
這兩個參數名稱是lhs 和rhs,分別意味"left-hand side"(左端)和"right-hand side"(右端)。我以它們做為二元運算子各函式的參數名稱,尤其是operator== 和算術運算子如operator*。舉個例子,如果a 和b 代表兩個分數(rational numbers)物件,而如果分數可經由一個non-member function operator* 相乘,那麼算式:

[pre]a * b[/pre]等於這款形式的函式呼叫:

[pre]operator*(a, b)[/pre]我將宣告operator* 如下(一如你在[url=mk:@MSITStore:E:/资料/c++/《Effective%20C++(中文版)》.chm::/file/ch06a.htm]條款23[/url]所見):

[pre]const Rational operator*(const Rational& lhs,const Rational& rhs);[/pre]如你所見,左運算元a 成為函式中的lhs,右運算元b 成為函式中的rhs。
我也利用縮寫字來為指標命名,規則如下:「指向型別T 之物件」的指標,我稱為pt,意思是"pointer to T"。下面是幾則例子:

[pre]string *ps; // ps = ptr to stringclass Airplane;Airplane *pa; // pa = ptr to Airplaneclass BankAccount;BankAccount *pba; // pba = ptr to BankAccount[/pre]對於references,我亦採用類似習慣。也就是說,rs 大約就是一個reference-tostring, ra 則可能是一個reference-to-Airplane。
當我談到member functions,偶而我會使用mf 這個名稱。
為避免任何混淆,任何時候我在書中提到「C 程式設計」時,我說的是ISO/ANSI 版的C 語言,而不是舊式的、沒那麼strongly-typed(強型式)的古典C 語言。

下载: 《Effective C++(中文版)》.rar (454 K)
转自通信仿真网,http://www.comsim.cn
原创粉丝点击