Effective C 讀書筆記之一

2021-05-27 21:28:05 字數 4588 閱讀 4168

條款1:視c++為乙個語言聯邦

這句話是從c++支援特別多的語言形式來說的,首先它支援過程形式,其次支援物件導向形式(主體),函式形式,泛型形式,元程式設計形式。這些能力和彈性使c++成為乙個無可匹敵的工具。

c++作為總語言可以細分為下面四個分語言:

* c語言    c相比於c++的侷限:沒有模板,沒有異常,沒有過載。

* object-orriented c++

* template c++ 由於templates威力強大,它們帶來的嶄新的程式設計規範,即所謂的 模板元程式設計(tmp:template metaprogramming)

* stl

條款2:盡量以const,enum,inline替換#define

首先你要知道#define並不被視為語言的一部分,這將導致一些問題:

#define aspect_ratio 1.653

記號名稱aspect_ratio也許從未被編譯器看見;也許在編譯器開始處處理源**之前就背預處理器移走了。於是記號名稱aspect_ratio有可能沒有進入符號表(symbol table)內。於是當你運用此常量但獲得乙個錯誤資訊時,可能帶來困惑,因為錯誤資訊也許會提到1.653而不是aspect_ratio。如果aspect_ratio被定義在乙個非你所寫的標頭檔案內,你肯定對1.653以及它來自何處毫無該奶奶,於是你將因為追蹤它而浪費時間。原因相同:你所使用的名稱可能並未進入符號表。

解決辦法是定義如下**替換之:

const double aspectratio = 1.653 ;

作為乙個語言常量,aspectratio肯定會被編譯器看到,當然就會進入符號表內。另外對浮點常量(如本例),使用常量可能比使用#define導致較小量的碼,因為預處理器「盲目地將巨集名稱aspect_ratio替換為1.653」可能導致目標碼出現多份1.653,若改為常量aspectradio絕不會出現相同情況。

如果你想寫乙個字串常量,可以如下寫:

const char* const authorname = 「jack.shi」 ;

因為string物件通常比其前輩char*-based合宜,所以上述的authorname往往定義成下面這樣:

const std::string authorname("jack.shi") ;

第二個值得注意的地方時class專屬常量。為了將常量的作用域限制於class內,你必須讓它成為乙個成員;而為確保此常量至多只有乙份實體,你必須讓它成為乙個static成員:

class gameplayer

然而你所看到的是numturns的宣告式而非定義式。通常c++要求你對你所使用的任何東西提供乙個定義式,但如果它是個class的專屬常量有是static且為整數型別(如ints,chars,bools),則需要特殊處理。只要不取它們的位址,你可以宣告並使用它們並無需提供定義式。但如果你取某個專屬常量的位址,或總是你不取其位址而你的編譯器卻(不正確地)堅持要看到乙個定義式,你就必須另外提供定義式如下:

const int gameplayer::numturns ;// numturns的定義,下面告訴你為什麼這裡沒有賦予數值

請把這個式子放進乙個實現檔案而非標頭檔案。由於class常量已在宣告時獲得初值(例如先前宣告的numturns它的初值設為5),因此定義時不可以再設為初值。

順帶一提,請注意,我們無法利用#define建立乙個class專屬常量,因為#defines並不重視作用域。一旦巨集被定義,它就在其後的編譯過程中有效(除非在某處被#undef)。這意味著#defines不僅不能用來定義class專屬常量,也不能夠提供任何封裝性,numturns就是。

舊式編譯器也許不支援上述語法,它們不允許static成員在其宣告式上獲得初值。此外所謂的「in-class初值設定」也只允許對整數常量進行。如果你的編譯器不支援上述語法,你可以將初值放在定義式:

// 標頭檔案

class costestimate

// 實現檔案

const double costestimate::fudgefactor = 1.35 ;

當你在class編譯期間需要乙個class常量值,例如在下述的 gameplayer::scores 的陣列宣告式中(是的,編譯器堅持必須在編譯期間知道陣列的大小)。這時候萬一你的編譯器(錯誤地)不允許 "static整數型class常量"完成"in class初值設定",可改用所謂的"the enum hack"補償做法。其理論是:"乙個屬於列舉型別的數值可權充ints被使用",於是gameplayer可定義如下:

class gameplayer

; int scores[numturns] ;// "the enum hack"--令numturns成為5的記號名稱,這就沒有問題了

} ;

基於數個理由enum hack值得我們認識。第一,enum hack 的行為某方面說比較像#define而不像const,有時候這正是你想要的。例如取乙個const的位址是合法的,但取乙個enum的位址就不合法,而取乙個#define的位址通常也不合法。如果你不想讓別人獲得乙個pointer或reference指向你的某個整數常量,enum可以幫助你實現這個約束。此外雖然優秀的編譯器不會為「整數型const物件「設定另外的儲存空間(除非你建立乙個pointer或reference指向該物件),不夠優秀的編譯器卻可能如此,而這這可能是你不想要的。enums和#defines一樣絕對不會導致非必要的記憶體分配。

認識enum hack的第二個理由純粹是為了使用主義。許多**用了它,所以看到它時你必須認知它。事實上"enum hack"是template metaprogramming(模板元程式設計)的基礎技術。

讓我們回到#define,因為#define很容易導致誤用的情況,請看如下的情況:

// 比較a和b的較大值呼叫f

#define call_with_max(a, b) f((a) > (b) ? (a):(b))

這樣的巨集光是看起來就讓人感覺痛苦不堪。

無論何時寫出這樣的巨集,你必須記得為巨集中的所有引數加上小括號,否則呼叫的時候會導致麻煩。但縱使你加上小括號,也會不會讓人不可思議的事情:

int a = 5,b = 0 ;

call_with_max(++a, b) ; // a被累加兩次

call_with_max(++a, b + 10) ; // a被累加一次

在這裡,在呼叫f之前,a的呼叫次數居然取決於」它拿來和誰比較「!

幸運的是你不需要為這種無聊的事情提供溫床。你可以獲得巨集帶來的效率以及一般函式的所有可預料行為和型別安全性--只要你寫出template inline函式

templateinline void callwithmax(const t& a,constt& b)// 由於我們不知道t是什麼,所以採用 pass by reference-to-const

有了consts,enums和inlines,我們對#define的需求降低了,但並非完全消除。#include仍然是必需品,而 #ifdef/#ifndef也繼續扮演控制編譯的重要角色。目前還不知道預處理全面引退的時候,但你應該明確地給予它更長更頻繁的假期。

請記住:

* 對於單純常量,最好以const物件或enums替換#defines。

* 對於形似函式的巨集,最好改用inline函式替換#defines。

Effective C 讀書筆記之七

條款八 別讓異常逃離析構函式 c 並不禁止析構函式吐出異常,但它不鼓勵你這樣做。這是有理由的。這是有理由的。考慮以下 class widget 假設這個可能吐出乙個異常 void dosomething 當vector被銷毀,它有責任銷毀其內含的widgets。假設v內含是個widgets,而在析構...

《effective C 》讀書筆記

1,c 關鍵字explicit c 中,乙個引數的 建構函式 或者除了第乙個引數外其餘引數都有預設值的多參建構函式 承擔了兩個角色。1 是個 構造器,2 是個預設且隱含的型別轉換操作符 所以,有時候在我們寫下如 aaa 這樣的 且恰好 的型別正好是aaa單引數構造器的引數型別,這時候 編譯器就自動呼...

Effective C 讀書筆記

一 讓自己習慣c 1 條款01 視c 為聯邦語言 c 的組成可分為四部分 1.c c 仍然以c語言為基礎。區塊 語句 預處理 內建資料型別 陣列 指標等都來自c。2.object oriented c c with classes所訴說的 classes 包括構造和析構 封裝 繼承 多型 virtu...