天天看點

C++程式設計規範之程式設計風格(讀書筆記)

第14條 甯要編譯時和連接配接時錯誤,也不要運作時錯誤。

摘要

能夠在編譯時做的事情,就不要推遲到運作時:編寫代碼時,應該在編譯期間使用編譯器檢查不變式(invariant),而不應該在運作時再進行檢查。運作時檢查取決于控制流和資料的具體情況,這意味着很難知道檢查是否徹底。相比而言,編譯時檢查與控制流和資料無關,一般情況下能夠獲得更高的可信度。

讨論

C++語言中提供了強大的靜态檢查功能,而對自動運作時檢查的支援則很少,使用靜态檢查功能可以帶來下列好處:

     靜态檢查與資料和控制流無關。

     靜态表示的模型更加可靠。

     靜态檢查不會帶來運作時開銷。

第15條 積極使用const

摘要

const是我們的朋友:不變的值更易于了解、跟蹤和分析,是以應該盡可能地使用常量代替變量,定義值的時候,應該把const作為預設的選項:常量很安全,在編譯時會對其進行檢查(見第14條),而且它與C++的類型系統已渾然一體。不要強制轉換const的類型,除非要調用常量不正确的函數(見第94條)。

第16條 避免使用宏

C++的宏的主要問題在于,它們表面上看起來很好,而實際上做的卻是另一回事。宏會忽略作用域,忽略類型系統,忽略所有其他的語言特性和規則,而且會劫持它為檔案其餘部分所定義(#define)的符号。

例外情況

宏仍然是幾個重要任務的惟一解決方案,比如#include保護符(guard)(見第24條),條件編譯中的#ifdef和#if defined,以及assert的實作(見第68條)。

在條件編譯(如與系統有關的部分)中,要避免在代碼中到處雜亂地插入#ifdef。相反,應該對代碼進行組織,利用宏來驅動一個公共接口的多個實作,然後始終使用該接口。

如果不想到處複制和粘貼代碼段,那麼可以使用宏(但要非常小心)。

第17條 避免使用“魔數”

摘要

程式設計并非魔術,是以不要故弄玄虛:要避免在代碼中使用諸如42和3.14159這樣的文字常量。它們本身沒有提供任何說明,并且因為增加了難于檢測的重複而使維護更加複雜。可以用符号名稱和表達式替換它們,比如width * aspectRatio。

第18條 盡可能局部地聲明變量

變量的生存期超過必需的長度時會産生以下幾個缺點:

     它們會使程式更難以了解和維護。

     它們的名字會污染上下文。

     它們不能總是被合理地初始化。

解決方案很簡單:盡可能局部地定義每個變量,通常就是在你有了足夠的資料進行初始化的時候,而且恰恰就在首次使用變量之前。

例外情況

有時候将變量提出循環是有好處的(見第9條)。

因為常量并不添加狀态,是以本條對常量不适用(見第17條)。

第19條 總是初始化變量

摘要

一切從白紙開始:未初始化的變量是C和C++程式中錯誤的常見來源。養成在使用記憶體之前先清除的習慣,可以避免這種錯誤,在定義變量的時候就将其初始化。

例外情況

硬體或者其他程序直接寫入的輸入緩沖區資料和volatile型資料不需要程式對其進行初始化。

第20條 避免函數過長,避免嵌套過深

摘要

短勝于長,平優于深:過長的函數和嵌套過深的代碼塊的出現,經常是因為沒能賦予一個函數以一個緊湊的職責所緻(見第5條),這兩種情況通常都能夠通過更好的重構予以解決。

讨論

請遵循這樣的常識和常理:限制函數的長度和嵌套深度。以下所有的合理建議對這一點都有所裨益:

     盡量緊湊:對一個函數隻賦予一種職責(見第5條)。

     不要自我重複:優先使用命名函數,而不要讓相似的代碼片斷反複出現。

     優先使用&&:在可以使用&&條件判斷的地方要避免使用連續嵌套的if。

     不要過分使用try:優先使用析構函數進行自動清除而避免使用try代碼塊(見第13條)。

     優先使用标準算法:算法比循環嵌套要少,通常也更好(見第84條)。

     不要根據類型标簽(type tag)進行分支(switch)。優先使用多态函數(見第90條)。

例外情況

如果一個函數的功能無法合理地重構為多個獨立的子任務(因為任何重構嘗試都需要傳遞許多局部變量和上下文,使重構結果的可讀性非但不好,反而更差),那麼它的較長和嵌套較多就是合理的。但是如果有幾個這樣的函數都具有相似的參數,那麼它們就有可能成為一個新類的成員。

第21條 避免跨編譯單元的初始化依賴

摘要

保持(初始化)順序:不同編譯單元中的名字空間級對象決不應該在初始化上互相依賴,因為其初始化順序是未定義的。這樣做會惹出很多麻煩,輕則在項目中稍做修改就會引發奇怪的崩潰,重則出現嚴重的不可移植問題——即使是同一編譯器的新版本也不行。

第22條 盡量減少定義性依賴。避免循環依賴

摘要

不要過分依賴:如果用前向聲明(forward declaration)能夠實作,那麼就不要包含(#include)定義。

不要互相依賴:循環依賴是指兩個子產品直接或者間接地互相依賴。所謂子產品就是一個緊湊的釋出單元(見“名字空間與子產品”部分的引言部分)。互相依賴的多個子產品并不是真正的獨立子產品,而是緊緊膠着在一起的一個更大的子產品,一個更大的釋出單元。是以,循環依賴有礙于子產品性,是大型項目的禍根。請避免循環依賴。

第23條 頭檔案應該自給自足

摘要

各司其責:應該確定所編寫的每個頭檔案都能夠獨自進行編譯,為此需要包含其内容所依賴的所有頭檔案。

第24條 總是編寫内部#include保護符,決不要編寫外部#include保護符

摘要

為頭(檔案)添加保護:在所有頭檔案中使用帶有惟一名稱的包含保護符(#include guard)防止無意的多次包含。

讨論

應該用内部包含保護符保護每個頭檔案,以避免在多次包含時重新定義。例如,頭檔案foo.h應該采用下面的一般形式:

#ifndef FOO_H_INCLUDED_

#define FOO_H_INCLUDED_

// ……檔案内容……

#endif

定義包含保護符時,應該遵守如下規則:

     保護符使用惟一名稱。

     不要自作聰明:不要在受保護部分的前後放置代碼或者注釋,要謹遵上面的标準形式。雖然如今的預處理器能夠檢測出包含保護符,但是它們的智商有限,隻認識正好位于頭檔案開始和結束處的保護代碼。

避免使用一些比較老的書中所提倡的已經過時了的外部包含保護符:

#ifndef FOO_H_INCLUDED_         // 不推薦

#include "foo.h"

#define FOO_H_INCLUDED_

#endif

例外情況

在一些非常罕見的情況下,可能需要多次包含一個頭檔案。 

繼續閱讀