條款1:指針與引用的區別
兩者之間的區別是:在任何狀況下都不能用指向空值的引用,而指針則能夠;指針能夠被從新賦值以指向另外一個不一樣的對象,可是引用則老是指向在初始化時被指定的對象,之後不能改變
在如下狀況下使用指針:一是存在不指向任何對象的可能性;二是須要可以在不一樣的時刻指向不一樣的對象
在如下狀況使用引用:老是指向一個對象且一旦指向一個對象以後就不會改變指向;重載某個操做符時,使用指針會形成語義誤解 ios
條款2:儘可能使用C++風格的類型轉換
static_cast:功能上基本上與C風格的類型轉換同樣強大,含義也同樣可是不能把struct轉換成int類型或者把double類型轉換成指針類型另外,它不能從表達式中去除const屬性
const_cast:用於類型轉換掉表達式的const或volatileness屬性可是不能用它來完成修改這兩個屬性以外的事情
dynamic_cast:用於安全地沿着類的繼承關係向下類型轉換失敗的轉換將返回空指針或者拋出異常
reinterpret_cast:這個操做符被用於的類型轉換的轉換結果時實現時定義所以,使用它的代碼很難移植最普通的用途就是在函數指針之間進行轉換 程序員
條款3:不要使用多態性數組
多態和指針算法不能混合在一塊兒使用,因此數組和多態也不能用在一塊兒
數組中各元素的內存地址是數組的起始地址加上以前各個元素的大小獲得的,若是各元素大小不一,那麼編譯器將不能正確地定位元素,從而產生錯誤 算法
條款4:避免無用的缺省構造函數
沒有缺省構造函數形成的問題:一般不可能創建對象數組,對於使用非堆數組,能夠在定義時提供必要的參數另外一種方法是使用指針數組,可是必須刪除數組裏的每一個指針指向的對象,並且還增長了內存分配量
提供無心義的缺省構造函數會影響類的工做效率,成員函數必須測試全部的部分是否都被正確的初始化 數據庫
條款5:謹慎定義類型轉換函數
缺省的隱式轉換將帶來出乎意料的結果,所以應該儘可能消除,使用顯式轉換函數經過不聲明運算符的方法,能夠克服隱式類型轉換運算符的缺點,經過使用explicit關鍵字和代理類的方法能夠消除單參數構造函數形成的隱式轉換 編程
條款6:自增和自減操做符前綴形式與後綴形式的區別
後綴式有一個int類型參數,當函數被調用時,編譯器傳遞一個0做爲int參數的值傳遞給該函數能夠在定義時省略掉不想使用的參數名稱,以免警告信息
後綴式返回const對象,緣由是 :使該類的行爲和int一致,而int不容許連續兩次自增後綴運算;連續兩次運算實際只增一次,和直覺不符
前綴比後綴效率更高,由於後綴要返回對象,而前綴只返回引用另外,能夠用前綴來實現後綴,以方便維護 數組
條款7:不要重載&&,||,或者,
對 於以上操做符來講,計算的順序是從左到右,返回最右邊表達式的值若是重載的話,不能保證其計算順序和基本類型想同操做符重載的目的是使程序更容易閱 讀,書寫和理解,而不是來迷惑其餘人若是沒有一個好理由重載操做符,就不要重載而對於&&,||和,,很難找到一個好理由 緩存
條款8:理解各類不一樣含義的new和delete
new操做符完成的功能分兩部分:第一部分是分配足夠的內存以便容納所需類型的對象;第二部分是它調用構造函數初始化內存中的對象new操做符老是作這兩件事,咱們不能以任何方式改變它的行爲
咱們能改變的是如何爲對象分配內存new操做符經過調用operator new來完成必需的內存分配,能夠重寫或重載這個函數來改變它的行爲能夠顯式調用operator來分配原始內存
若是已經分配了內存,須要以此內存來構造對象,可使用placement new,其調用形式爲new(void* buffer)class(int size)
對於delete來講,應該和new保持一致,怎樣分配內存,就應該採用相應的辦法釋放內存
operator new[]與operator delete[]和new與delete相相似 安全
條款9:使用析構函數防止資源泄漏
使用指針時,若是在delete指針以前產生異常,將會致使不能刪除指針,從而產生資源泄漏
解決辦法:使用對象封裝資源,如使用auto_ptr,使得資源可以自動被釋放 數據結構
條款10:在構造函數中防止資源泄漏
類中存在指針時,在構造函數中須要考慮出現異常的狀況:異常將致使之前初始化的其它指針成員不能刪除,從而產生資源泄漏解決辦法是在構造函數中考慮異常處理,產生異常時釋放已分配的資源最好的方法是使用對象封裝資源 函數
條款11:禁止異常信息傳遞到析構函數外
禁止異常傳遞到析構函數外的兩個緣由:第一可以在異常傳遞的堆棧展轉開解的過程當中,防止terminate被調用;第二它能幫助確保析構函數總能完成咱們但願它作的全部事情
解決方法是在析構函數中使用try-catch塊屏蔽全部異常
條款12:理解拋出一個異常與傳遞一個參數或調用一個虛函數間的差別
有 三個主要區別:第一,異常對象在傳遞時總被進行拷貝當經過傳值方式捕獲時,異常對象被拷貝了兩次對象做爲參數傳遞給函數時不須要被拷貝;第二,對象做 爲異常被拋出與做爲參數傳遞給函數相比,前者類型轉換比後者少(前者只有兩種轉換形式:繼承類與基類的轉換,類型化指針到無類型指針的轉換);最後一點, catch子句進行異常類型匹配的順序是它們在源代碼中出現的順序,第一個類型匹配成功的擦他處將被用來執行當一個對象調用一個虛函數時,被選擇的函數 位於與對象類型匹配最佳的類裏,急事該類不是在源代碼的最前頭
條款13:經過引用捕獲異常
有三個選擇能夠捕獲異常:第一指 針,創建在堆中的對象必需刪除,而對於不是創建在堆中的對象,刪除它會形成不可預測的後果,所以將面臨一個難題:對象創建在堆中仍是不在堆中;第二傳 值,異常對象被拋出時系統將對異常對象拷貝兩次,並且它會產生對象切割,即派生類的異常對象被做爲基類異常對象捕獲時,它的派生類行爲就被切割調了 這樣產生的對象其實是基類對象;第三引用,完美解決以上問題
條款14:審慎使用異常規格
避免調用unexpected函數 的辦法:第一避免在帶有類型參數的模板內使用異常規格由於咱們沒有辦法知道某種模板類型參數拋出什麼樣的異常,因此不可能爲一個模板提供一個有意義的 異常規格;第二若是在一個函數內調用其它沒有異常規格的函數時應該去除這個函數的異常規格;第三處理系統自己拋出的異常能夠將全部的 unexpected異常都被替換爲自定義的異常對象,或者替換unexpected函數,使其從新拋出當前異常,這樣異常將被替換爲 bad_exception,從而代替原來的異常繼續傳遞
很容易寫出違反異常規格的代碼,因此應該審慎使用異常規格
條款15:瞭解異常處理的系統開銷
三 個方面:第一須要空間創建數據結構來跟蹤對象是否被徹底構造,還須要系統時間保持這些數據結構不斷更新;第二try塊不管什麼時候使用它,都得爲此付出 代價編譯器爲異常規格生成的代碼與它們爲try塊生成的代碼同樣多,因此一個異常規格通常花掉與try塊同樣多的系統開銷第三拋出異常的開銷由於 異常不多見,因此這樣的事件不會對整個程序的性能形成太大的影響
條款16:牢記8020準則
8020準則說的是大約20%的代碼使用了80%的程序資源,即軟件總體的性能取決於代碼組成中的一小部分使用profiler來肯定程序中的那20%,關注那些局部效率可以被極大提升的地方
條款17:考慮使用懶惰計算法
懶惰計算法的含義是拖延計算的時間,等到須要時才進行計算其做用爲:能避免不須要的對象拷貝,經過使用operator[]區分出讀寫操做,避免不須要的數據庫讀取操做,避免不須要的數字操做可是,若是計算都是重要的,懶惰計算法可能會減慢速度並增長內存的使用
條款18:分期攤還指望的計算
核心是使用過分熱情算法,有兩種方法:緩存那些已經被計算出來而之後還有可能須要的值;預提取,作比當前須要作的更多事情
當必須支持某些操做而不總須要其結果時,可使用懶惰計算法提升程序運行效率;當必須支持某些操做而其結果幾乎老是被須要或不止一次地須要時,可使用過分熱情算法提升程序運行效率
條款19:理解臨時對象的來源
臨時對象產生的兩種條件:爲了是函數成功調用而進行隱式類型轉換和函數返回對象時
臨時對象是有開銷的,所以要儘量去消除它們,然而更重要的是訓練本身尋找可能創建臨時對象的地方在任什麼時候候只要見到常量引用參數,就存在創建臨時對象而綁定在參數上的可能性在任什麼時候候只要見到函數返回對象,就會有一個臨時對象被創建(之後被釋放)
條款20:協助完成返回值優化
應當返回一個對象時不要試圖返回一個指針或引用
C+ +規則容許編譯器優化不出現的臨時對象,全部最佳的辦法莫過於:retrun Ratinal(lhs.numerator()*rhs.numerator(), lhs.denominator()*rhs.denominator())這種優化是經過使用函數的retuan location(或者用在一個函數調用位置的對象來替代),來消除局部臨時對象,這種優化還有一個名字:返回值優化
條款21:經過重載避免隱式類型轉換
隱式類型轉換將產生臨時對象,從而帶來額外的系統開銷
解決辦法是使用重載,以免隱式類型轉換要注意的一點是在C++中有一條規則是每個重載的operator必須帶有一個用戶定義類型的參數(這條規定是有道理的,若是沒有的話,程序員將能改變預約義的操做,這樣作確定吧程序引入混亂的境地)
另外,牢記8020規則,沒有必要實現大量的重載函數,除非有理由確信程序使用重載函數後總體效率會有顯著提升
條款22:考慮用運算符的賦值形式取代其單獨形式
運算符的賦值形式不須要產生臨時對象,所以應該儘可能使用對運算符的單獨形式的最佳實現方法是return Rational(lhs) += rhs;這種方法將返回值優化和運算符的賦值形式結合起來,即高效,又方便
條款23:考慮變動程序庫
程序庫必須在效率和功能等各個方面有各自的權衡,所以在具體實現時應該考慮利用程序庫的優勢例如程序存在I/O瓶頸,就能夠考慮用stdio替代iostream
條款24:理解虛擬函數多繼承虛基類和RTTI所需的代價
虛函數所需的代價:必須爲每一個包含虛函數的類的virtual table留出空間;每一個包含虛函數的類的對象裏,必須爲額外的指針付出代價;實際上放棄了使用內聯函數
多繼承時,在單個對象裏有多個vptr(一個基類對應一個)它和虛基類同樣,會增長對象體積的大小
RTTI能讓咱們在運行時找到對象和類的有關信息,因此確定有某個地方存儲了這些信息,讓咱們查詢這些信息被存儲在類型爲type_info的對象裏,能夠經過typeid操做符訪問到一個類的typeid對象一般,RTTI被設計爲在類的vbtl上實現
條款25:將構造函數和非成員函數虛擬化
構 造函數的虛擬化看似無心義,可是在實際當中有必定的用處例如,在類中構建一個虛擬函數,其功能僅僅是實現構造函數,就能夠對外界提供一組派生類的公共構 造接口虛擬拷貝構造函數也是能夠實現的,可是要利用到最近才被採納的較寬鬆的虛擬函數返回值類型規則被派生類重定義的虛擬函數不用必須與基類的虛擬函 數具備同樣的返回類型
具備虛擬行爲的非成員函數很簡單首先編寫一個虛擬函數完成工做,而後再寫衣一個非虛擬函數,它什麼也不作只是調用這個函數,可使用內聯來避免函數調用的開銷
條款26:限制某個類所能產生的對象數量
只 有一個對象:使用單一模式,將類的構造函數聲明爲private,再聲明一個靜態函數,該函數中有一個類的靜態對象不將該靜態對象放在類中緣由是放在函 數中時,執行函數時才創建對象,而且對象初始化時間肯定的,即第一次執行該函數時另外,該函數不能聲明爲內聯,若是內聯可能形成程序的靜態對象拷貝超過 一個
限制對象個數:創建一個基類,構造函數中計數加一,若超過最大值則拋出異常;析構函數中計數減一
編程點滴:
將模板類的定義和實現放在一個文件中,不然將形成引用未定義錯誤(血的教訓);
靜態數據成員須要先聲明再初始化;
用常量值做初始化的有序類型的const靜態數據成員是一個常量表達式(能夠做爲數組定義的維數);
構造函數中拋出異常,將致使靜態數組成員從新初始化
條款27:要求或禁止在堆中產生對象
在堆中的對象不必定是用new分配的對象,例如成員對象,雖然不是用new分配的可是仍然在堆中
要 求在堆中創建對象能夠將析構函數聲明未private,再創建一個虛擬析構函數進行對象析構此時若是創建非堆對象將致使析構函數不能經過編譯固然也可 以將構造函數聲明爲private,可是這樣將致使必須聲明n個構造函數(缺省,拷貝等等)爲了解決繼承問題,能夠將其聲明爲protected,解決 包容問題則只能將其聲明爲指針
沒有辦法不能判斷一個對象是否在堆中,可是能夠判斷一個對象是否能夠安全用delete刪除,只需在operator new中將其指針加入一個列表,而後根據此列表進行判斷
把一個指針dynamic_cast成void*類型(或const void*或volatile void*等),生成的指針將指向原指針指向對象內存的開始處可是dynamic_cast只能用於指向至少具備一個虛擬函數的對象的指針上
禁止創建堆對象能夠簡單的將operator new聲明爲private,可是仍然不能判斷其是否在堆中
條款28:靈巧(smart)指針
靈巧指針的用處是能夠對操做進行封裝,同一用戶接口
靈巧指針從模板生成,由於要與內建指針相似,必須是強類型的;模板參數肯定指向對象的類型
靈巧指針的拷貝和賦值,採起的方案是當auto_ptr被拷貝和賦值時,對象全部權隨之被傳遞此時,經過傳值方式傳遞靈巧指針對象將致使不肯定的後果,應該使用引用
記住當返回類型是基類而返回對象實際上派生類對象時,不能傳遞對象,應該傳遞引用或指針,不然將產生對象切割
測試靈巧指針是否爲NULL有兩種方案:一種是使用類型轉換,將其轉換爲void*,可是這樣將致使類型不安全,由於不一樣類型的靈巧指針之間將可以互相比較;另外一種是重載operator!,這種方案只能使用!ptr這種方式檢測
最好不要提供轉換到內建指針的隱式類型轉換操做符,直接提供內建指針將破壞靈巧指針的靈巧特性
靈巧指針的繼承類到基類的類型轉換的一個最佳解決方案是使用模板成員函數,這將使得內建指針全部能夠轉換的類型也能夠在靈巧指針中進行轉換可是對於間接繼承的狀況,必須用dynamic_cast指定其要轉換的類型是直接基類仍是間接基類
爲了實現const靈巧指針,能夠新建一個類,該類從非const靈巧指針繼承這樣的化,const靈巧指針能作的,非const靈巧指針也能作,從而與標準形式相同
條款29:引用計數
使用引用計數後,對象本身擁有本身,當沒有人再使用它時,它本身自動銷燬本身所以,引用計數是個簡單的垃圾回收體系
在基類中調用delete this將致使派生類的對象被銷燬
寫時拷貝:與其它對象共享一個值直到寫操做時才擁有本身的拷貝它是Lazy原則的特例
精彩的類層次結構:
RCObject類提供計數操做;StringValue包含指向數據的指針並繼承RCObject的計數操做;RCPtr是一個靈巧指針,封裝了本屬於String的一些計數操做
條款30:代理類
能夠用兩個類來實現二維數組:Array1D是一個一維數組,而Array2D則是一個Array1D的一維數組Array1D的實例扮演的是一個在概念上不存在的一維數組,它是一個代理類
代 理類最神奇的功能是區分經過operator[]進行的是讀操做仍是寫操做,它的思想是對於operator[]操做,返回的不是真正的對象,而是一個 proxy類,這個代理類記錄了對象的信息,將它做爲賦值操做的目標時,proxy類扮演的是左值,用其它方式使用它,proxy類扮演的是右值用賦值 操做符來實現左值操做,用隱式類型轉換來實現右值操做
用proxy類區分operator[]做左值仍是右值的侷限性:要實現proxy類和原類型的無縫替代,必須申明原類型的一整套操做符;另外,使用proxy類還有隱式類型轉換的全部缺點
編程點滴:不能將臨時對象綁定爲非const的引用的行參
條款31:讓函數根據一個以上的對象來決定怎麼虛擬
有 三種方式:用虛函數加RTTI,在派生類的重載虛函數中使用if-else對傳進的不一樣類型參數執行不一樣的操做,這樣作幾乎放棄了封裝,每增長一個新的類 型時,必須更新每個基於RTTI的if-else鏈以處理這個新的類型,所以程序本質上是沒有可維護性的;只使用虛函數,經過幾回單獨的虛函數調用,第 一次決定第一個對象的動態類型,第二次決定第二個對象動態類型,如此這般然而,這種方法的缺陷仍然是:每一個類必須知道它的全部同胞類,增長新類時,全部 代碼必須更新;模擬虛函數表,在類外創建一張模擬虛函數表,該表是類型和函數指針的映射,加入新類型是不須改動其它類代碼,只需在類外增長一個處理函數即 可
條款32:在將來時態開發程序
將來時態的考慮只是簡單地增長了一些額外約束:
提供完備的類,即便某些部分如今尚未被使用
將接口設計得便於常見操做並防止常見錯誤使得類容易正確使用而不易用錯
若是沒有限制不能通用化代碼,那麼通用化它
條款33:將非尾端類設計爲抽象類
若是有一個實體類公有繼承自另外一個實體類,應該將兩個類的繼承層次改成三個類的繼承層次,經過創造一個新的抽象類並將其它兩個實體類都從它繼承所以,設計類層次的通常規則是:非尾端類應該是抽象類在處理外來的類庫,可能不得不違反這個規則
編程點滴:抽象類的派生類不能是抽象類;實現純虛函數通常不常見,但對純虛析構函數,它必須實現
條款34:如何在同一程序中混合使用C++和C
混合編程的指導原則:
確保C++和C編譯器產生兼容的obj文件
將在兩種語言下都使用的函數申明爲extern C
只要可能,用C++寫main()
總用delete釋放new分配的內存;總用free釋放malloc分配的內存
將在兩種語言間傳遞的東西限制在用C編譯的數據結構的範圍內;這些結構的C++版本能夠包含非虛成員函數
條款35:讓本身習慣使用標準C++語言
STL 基於三個基本概念:包容器(container)選擇子(iterator)和算法(algorithms)包容器是被包容的對象的封裝;選擇子是類 指針的對象,讓你能如同使用指針操做內建類型的數組同樣操做STL的包容器;算法是對包容器進行處理的函數,並使用選擇子來實現
條款1:儘可能用const和inline而不用#define
1.爲方便調試,最好使用常量
注意:常量定義通常放在頭文件中,可將指針和指針所指的類型都定義成const,如const char * const authorName = Scott Meyers;
類中常量一般定義爲靜態成員, 並且須要先聲明後定義能夠在聲明時或定義時賦值,也可以使用借用enum的方法如enum{Num = 5};
2.#define語句形成的問題
如#define max(a, b) ((a) > (b) ? (a) : (b))
在下面狀況下:
Int a= 5, b = 0;
max(++ a, b);
max(++ a, b + 10);
max內部發生些什麼取決於它比較的是什麼值解決方法是使用inline函數,可使用template來產生一個函數集
條款2:儘可能用而不用
用>> 和<<使得編譯器本身能夠根據不一樣的變量類型選擇操做符的不一樣形式,而採起的語法形式相同
條款3:儘可能用new和delete而不用malloc和free
使用malloc和free的時候不會本身調用構造函數和析構函數,所以若是對象本身分配了內存的話,那麼這些內存會所有丟失另外,將new和malloc混用會致使不可預測的後果
條款4:儘可能使用C++風格的註釋
C++的註釋能夠在註釋裏還有註釋,因此註釋掉一個代碼塊不用刪除這段代碼的註釋C則不行
條款5:對應的new和delete要採用相同的形式
調 用new時用了[],調用delete時也要用 []若是調用new時沒有用[],那調用delete時也不要用[]對於typedef來講,用new建立了一個typedef定義的類型的對象後, delete時必須根據typedef定義的類型來刪除所以,爲了不混亂,最好杜絕數組類型用typedef
條款6:析構函數裏對指針成員調用delete
刪除空指針是安全的,所以在析構函數裏能夠簡單的delete類的指針成員,而不用擔憂他們是否被new過
條款7:預先準備好內存不足的狀況
1.用try-cache來捕獲拋出的異常
2. 當內存分配請求不能知足時,調用預先指定的一個出錯處理函數這個方法基於一個常規,即當operator new不能知足請求時,會在拋出異常以前調用客戶指定的一個出錯處理函數通常稱之爲new-handler函數還能夠建立一個混合風格的基類這種基 類容許子類繼承它某一特定的功能(即函數)
條款8:寫operator new和operator delete時要遵循常規
內存分配程序支持new-handler函數並正確地處理了零內存請求,而且內存釋放程序處理了空指針此外還必需要有正確的返回值
條款9:避免隱藏標準形式的new
在 類裏定義了一個稱爲operator new的函數後,會不經意地阻止了對標準new的訪問(到底如何隱藏的???)一個辦法是在類裏寫一個支持標準new調用方式的operator new,它和標準new作一樣的事,這能夠用一個高效的內聯函數來封裝實現另外一種方法是爲每個增長到operator new的參數提供缺省值
條款10:若是寫了operator new就要同時寫operator delete
operator new和operator delete須要同時工做,若是寫了operator new,就必定要寫operator delete對於爲大量的小對象分配內存的狀況,能夠考慮使用內存池,以犧牲靈活性來換取高效率
條款11:爲須要動態分配內存的類聲明一個拷貝構造函數和一個賦值操做符
若是沒有自定已拷貝構造函數和賦值操做符,C++會生成並調用缺省的拷貝構造函數和賦值操做符,它們對對象裏的指針進行逐位拷貝,這會致使內存泄漏和指針重複刪除所以,只要類裏有指針時,就要寫本身版本的拷貝構造函數和賦值運算符函數
條款12:儘可能使用初始化而不要在構造函數裏賦值
儘可能使用成員初始化列表,一方面對於成員來講只需承擔一次拷貝構造函數的代價,而非構造函數裏賦值時的一次(缺省)構造函數和一次賦值函數的代價;另外一方面const和引用成員只能被初始化而不能被賦值
條款13:初始化列表中的成員列出的順序和它們在類中聲明的順序相同
類的成員是按照它們在類裏被聲明的順序進行初始化的,和它們在成員初始化列表中列出的順序沒有關係
條款14:肯定基類有虛析構函數
經過基類的指針去刪除派生類的對象,而基類有沒有虛析構函數時,結果將是不可肯定的所以必須將基類的析構函數聲明爲virtual可是,無端的聲明虛析構函數和永遠不去聲明同樣是錯誤的,聲明虛函數將影響效率
條款15:讓operator=返回*this的引用
當 定義本身的賦值運算符時,必須返回賦值運算符左邊參數的引用,*this若是不這樣作,就會致使不能連續賦值,或致使調用時的隱式類型轉換不能進行(隱 式類型轉換時要用到臨時對象,而臨時對象是const的),或兩種狀況同時發生對於沒有聲明相應參數爲const的函數來講,傳遞一個const對象是 非法的
條款16:在operator=中對全部數據成員賦值
當類裏增長新的數據成員時,要記住更新賦值運算符函數對基類的私有成員賦值時,能夠顯示調用基類的operator=函數派生類的拷貝構造函數中必須調用基類的拷貝構造函數而不是缺省構造函數,不然基類的數據成員將不能初始化
條款17:在operator=中檢查給本身賦值的狀況
顯 示的本身給本身賦值不常見,可是程序中可能存在隱式的自我賦值:一個對象的兩個不一樣名字(引用)互相賦值首先,若是檢查到本身給本身賦值就當即返回,可 以節省大量的工做;其次,一個賦值運算符必須首先釋放掉一個對象的資源,而後根據新值分配新的資源,在本身給本身的狀況下,釋放舊的資源將是災難性的
條款18:爭取使類的接口完整而且最小
必要的函數是拷貝構造函數,賦值運算符函數,而後在此基礎上選擇必要的方便的函數功能進行添加
條款19:分清成員函數,非成員函數和友元函數
虛函數必須是成員函數若是f必須是虛函數,就讓它稱爲類c的成員函數
ioerator>>和operator<<決不能是成員函數若是f是operator>>或operator<<,讓f稱爲非成員函數若是f還須要 訪問c的非公有成員,讓f稱爲c的友元
其它狀況下都聲明爲成員函數若是以上狀況都不是,讓f稱爲c的成員函數
Result = onehalf * 2;能經過編譯的緣由:調用重載*操做符的成員函數,對參數2進行隱式類型轉換
Result = 2 * onehalf;不能經過編譯的緣由:不能對成員函數所在對象(即成員函數中this指針指向的對象)進行轉換
條款20:避免public接口出現數據成員
訪問一致性,public接口裏都是函數
精確的訪問控制,能夠精確設置數據成員的讀寫權限
功能分離,能夠用一段計算來取代一個數據成員舉例:計算汽車行駛的平均速度
條款21:儘可能使用const
若是const出如今*號左邊,指針指向的數據爲常量;若是const出如今*號右邊,則指針自己爲常量;若是const在兩邊都出現,兩者都是常量
將operator的返回結果聲明爲const,以防止對返回結果賦值,這樣不合常規
c+ +中的const:成員函數不修改對象中的任何數據成員時,即不修改對象中的任何一個比特時,這個成員函數纔是const的形成的問題是能夠修改指針指 向的值,並且不能修改對象中的一些必要修改的值解決方案是將必要修改的成員運用mutable關鍵字另外一種方法是使用const_cast初始化一個 局部變量指針,使之指向this所指的同一個對象來間接實現還有一種有用又安全的方法:在知道參數不會在函數內部被修改的狀況下,將一個const對象 傳遞到一個取非const參數的函數中
條款22:儘可能用傳引用而不用傳值
傳值將致使昂貴的對象開銷,而傳引用則很是高效
傳引用避免了切割問題,即當一個派生類的對象做爲基類對象被傳遞是,派生類的對象的做爲派生類所具備的全部行爲特性會被切割掉,從而變成了一個簡單的基類對象
條款23:必須返回一個對象時不要試圖返回一個引用縮寫
典型狀況:操做符重載
常見的錯誤:
返回引用,返回的是局部對象的引用
堆中構造,使用new分配內存,可是無人負責delete的調用,從而形成內存泄漏
返回靜態對象,致使調用同一函數比較時老是相等
正確的方法是直接在堆棧中建立對象並返回
條款24:在函數重載和設定參數缺省值間慎重選擇
若是能夠選擇一個合適的缺省參數,不然就使用函數重載
有一些狀況必須使用重載:函數的結果取決於傳入參數的個數;須要完成一項特殊的任務
條款25:避免對指針和數字類型重載
對於f(0):0表明int仍是null編譯器認爲是int,這和人們的想象不同解決辦法是使用成員模板,構造一個能夠產生null指針對象的類最重要的是,只要有可能,就要避免對一個數字和一個指針類型重載
條款26:小心潛在二義性
情形1:能夠經過構造函數和轉換運算符產生另外一個類的對象,這時編譯器將拒絕對其中的一種方法進行選擇
情形2:f(int);f(char);對於f(double)時產生二義
情形3:多繼承時,兩個基類有同名的成員此時必須指定基類方可調用,而不考慮訪問控制權限和返回值
條款27:若是不想使用隱式生成的函數就要顯式地禁止它
方法是聲明該函數,並使之爲private顯式地聲明一個成員函數,就防止了編譯器去自動生成它的版本;使函數爲private,就防止了別人去調用它爲了防止成員函數和友元函數的調用,只聲明而不定義這個函數
條款28:劃分全局名字空間
使用名字空間,以防止不一樣庫的名字衝突對於不支持名字空間的編譯器,可使用struct來模擬名字空間,可是此時運算符只能經過函數調用來使用
條款29:避免返回內部數據的句柄
對於const成員函數來講,返回句柄可能會破壞數據抽象若是返回的不是指向const數據的句柄,數據可能被修改對非const成員函數來講,返回句柄會帶來麻煩,特別是涉及到臨時對象時句柄就象指針同樣,能夠是懸浮的
條款30:避免這樣的成員函數:其返回值是指向成員的非const指針或引用,但成員的訪問級比這個函數要低
若是得到了私有或保護成員(包括成員函數)的地址(指針或引用),那麼就能夠象對待公有成員同樣進行訪問若是不得不返回其引用或指針,能夠經過返回指向const對象的指針或引用來達到一箭雙鵰的效果
條款31:千萬不要返回局部對象的引用,也不要返回函數內部用new初始化的指針的引用
如 果返回局部對象的引用,那個局部對象其實已經在函數調用者使用它以前就被銷燬了而返回廢棄指針的問題是必需要有人負責調用delete,並且對於 product=one*two*three*four;的狀況,將產生內存泄漏所以,寫一個返回廢棄指針的函數無異於坐等內存泄漏的來臨
條款32:儘量地推遲變量的定義
不 僅要強變量的定義推遲到必須使用它的時候,還要儘可能推遲到能夠爲它提供一個初始化參數位置這樣作,不只能夠避免對沒必要要的對象進行構造和析構,還能夠避 免無心義的對缺省構造函數的調用並且,在對變量初始化的場合下,變量自己的用途不言自明,在這裏定義變量有益於代表變量的含義
條款33:明智使用內聯
內聯函數的本質是將每一個函數調用以它的代碼體來替換
大多數編譯器拒絕複雜的內聯函數(例如,包含循環和遞歸的函數);還有,即便是最簡單的虛函數調用,編譯器的內聯處理程序對它也心有餘而力不足
若編譯器不進行內聯,則將內聯函數看成通常的外聯函數來處理這稱爲被外聯的內聯
找出重要的函數,將它內聯同時要注意代碼膨脹帶來的問題,並監視編譯器的警告信息,看看是否有內聯函數沒有被編譯器內聯
條款34:將文件間的編譯依賴性降至最低
若是可使用對象的引用和指針,就要避免使用對象自己定義某個類型的引用和指針只會涉及到這個類型的聲明,定義此類型的對象則須要類型定義的參與
儘量使用類的聲明,而不使用類的定義由於在聲明一個函數時,若是用到某個類,是絕對不須要這個類的定義的,即便函數是經過傳值來傳遞和返回這個類
不要在頭文件中再包含其它頭文件,除非缺乏了它們就不能編譯相反,要一個一個地聲明所須要的類,讓使用這個頭文件的用戶本身去包含其它的頭文件
最後一點,句柄類和協議類都不大會使用類聯函數使用任何內聯函數時都要訪問實現細節,而設計句柄類和協議類的初衷正是爲了不這種狀況
條款35:使公有繼承體現是一個的含義
若是類D從類B公有繼承時,類型D的每個對象也是類型B的一個對象,但反之不成立任何可使用類型B的對象的地方,類型D的對象也可使用
特別注意通常理解中的是一個,好比企鵝是鳥,並不嚴密若是涉及到飛這個動做,兩者之間不適合使用公有繼承
條款36:區分接口繼承課實現繼承
定義純虛函數的目的在於,使派生類僅僅只是繼承函數的接口也能夠爲純虛函數提供一種缺省實現
聲明簡單虛函數的目的在於,使派生類繼承函數的接口和缺省實現
聲明非虛函數的目的在於,使派生類繼承函數的接口和強制性實現
條款37:毫不要從新定義繼承而來的非虛函數
若是從新定義繼承而來的非虛函數,將致使對象對函數的調用結果由指向其的指針決定,而不是由對象自己的類型來決定另外,也是類的設計產生矛盾,由於公有繼承的含義是是一個,改變繼承而來的方法顯然是不合理的
條款38:毫不要從新定義繼承而來的缺省參數值
虛函數動態綁定,而缺省參數是靜態綁定所以從新定義繼承而來的缺省參數值可能形成調用的是定義在派生類,但使用了基類中缺省參數值的虛函數
條款39:避免向下轉換繼承層次
採用向下轉換時,將不利於對代碼進行維護,能夠採用虛函數的方法來解決
不得不進行向下轉換時,採用安全的向下轉換:dynamic_cast運算符dynamic_cast運算符先嚐試轉換,若轉換成功就返回新類型的合法指針,若失敗則返回空指針
條款40:經過分層來體現有一個或用來實現
公有繼承的含義是是一個對應地,分層的含義是有一個或用來實現例如,要實現set類,由於list中能夠包含重複元素,所以set不是一個listset能夠用list來實現,即在set中包含一個list
條款41:區分繼承和模板
當對象的類型不影響類中函數的行爲時,就要使用模板來生成這樣一組類
當對象的類型影響類中函數的行爲時,就要使用繼承來獲得這樣一組類
條款42:明智地使用私有繼承
關於私有繼承的兩個規則:和公有繼承相反,若是兩個類之間的繼承關係爲私有,編譯器通常 不會將派生類對象轉換爲基類對象;從私有基類繼承而來的成員都稱爲了派生類的私有成員,即便它們在基類中是保護或公有成員
私有繼承意味這用來實現,可是應儘量使用分層,必須時才使用私有繼承
條款43:明智地使用多繼承
多 繼承後果:二義性,若是一個派生類從多個基類繼承了一個成員名,全部對這個名字的訪問都是二義的,你必須明確說出你所指的是哪一個成員這可能致使虛函數的 失效,而且不能對多繼承而來的幾個相同名稱虛函數同時進行重定義鑽石型繼承,此時向虛基類傳遞構造函數參數時要在繼承結構中最底層派生類的成員初始化列 表中指定同時還要仔細想一想虛函數的優先度
然而在適當時候仍是可使用多繼承,例如將接口的公有繼承和實現的私有繼承結合起來的狀況
以增長中間類的代價來消除多繼承有時侯是值得的通常應該避免使用多繼承以減小繼承結構的複雜性
條款44:說你想說的,理解你所說的
理解不一樣的面向對象構件在C++中的含義:
· 共同的基類意味着共同的特性
· 公有繼承意味着 是一個
· 私有繼承意味着 用來實現
· 分層意味着 有一個 或 用來實現
下面的對應關係只適用於公有繼承的狀況:
· 純虛函數意味着僅僅繼承函數的接口
· 簡單虛函數意味着繼承函數的接口加上一個缺省實現
· 非虛函數意味着繼承函數的接口加上一個強制實現
條款45:弄清C++在幕後爲你所寫所調用的函數
如 果沒有聲明下列函數,編譯器會聲明它本身的版本:一個拷貝構造函數,一個賦值運算符,一個析構函數,一對取址運算符和一個缺省構造函數對於拷貝構造函數 和賦值運算符,官方的規則是:缺省拷貝構造函數(賦值運算符)對類的非靜態數據成員進行以成員爲單位的逐一拷貝構造(賦值)
特別要注意因爲編譯器自動生成的函數形成的編譯錯誤
條款46:寧肯編譯和連接是出錯,也不要運行時出錯
一般,對設計作一點小小的改動,就能夠在編譯期間消除可能產生的運行時錯誤這經常涉及到在程序中增長新的數據類型例如對於須要類型檢查的Month,能夠將其設爲一個Month類:構造函數私有,產生對象使用靜態成員函數,每一個Month對象爲const
條款47:確保非局部靜態對象在使用前被初始化
若是在某個被編譯單元中,一個對象的初始化要依賴於另外一個被編譯單元中的另外一個對象的值,而且這第二個對象自己也須要初始化,就有可能形成混亂
雖 然關於 非局部 靜態對象何時被初始化,C++幾乎沒有作過說明;但對於函數中的靜態對象(即,局部 靜態對象)何時被初始化,C++卻明確指出:它們在函數調用過程當中初次碰到對象的定義時被初始化若是不對非局部靜態對象直接訪問,而用返回局部靜態 對象引用的函數調用來代替,就能保證從函數獲得的引用指向的是被初始化了的對象
條款48:重視編譯器警告
重視編譯器產生的每一條警告信息在忽略一個警告以前,必定要準確理解它想告訴你的含義
條款49:熟悉標準庫
對 於C++頭文件,最大的挑戰是把字符串頭文件理清楚:是舊的C頭文件,對應的是基於char*的字符串處理函數; 是包裝了std的C++頭文件,對應的是新的string類(看下文);是對應於舊C頭文件 的std版本若是能掌握這些,其他的也就容易了
庫中的一切都是模板
條款50:提升對C++的認識 C++的設計目標:和C兼容,效率,和傳統開發工具及環境的兼容性,解決真實問題的可應用性 參考C++標準,理解C++的設計過程