from http://code.google.com/p/google-styleguide/ 程序員
版本:算法
3.133數據庫
原做者:編程
Benjy Weinberger數組
Craig Silverstein緩存
Gregory Eitzmann安全
Mark Mentovai多線程
Tashana Landray併發
翻譯:框架
項目主頁:
PS: 能夠對比 Linus的 《Linux內核代碼風格》http://blog.csdn.net/shendl/article/details/6230836 的C風格指南閱讀,看看C和C++對風格的不一樣要求。
C++ 是 Google 大部分開源項目的主要編程語言. 正如每一個 C++ 程序員都知道的, C++ 有不少強大的特性, 但這種強大不可避免的致使它走向複雜,使代碼更容易產生 bug, 難以閱讀和維護.
本指南的目的是經過詳細闡述 C++ 注意事項來駕馭其複雜性. 這些規則在保證代碼易於管理的同時, 高效使用 C++ 的語言特性.
風格, 亦被稱做可讀性, 也就是指導 C++ 編程的約定. 使用術語 「風格」 有些用詞不當, 由於這些習慣遠不止源代碼文件格式化這麼簡單.
使代碼易於管理的方法之一是增強代碼一致性. 讓任何程序員均可以快速讀懂你的代碼這點很是重要. 保持統一編程風格並遵照約定意味着能夠很容易根據 「模式匹配」 規則來推斷各類標識符的含義. 建立通用, 必需的習慣用語和模式能夠使代碼更容易理解. 在一些狀況下可能有充分的理由改變某些編程風格, 但咱們仍是應該遵循一致性原則,儘可能不這麼作.
本指南的另外一個觀點是 C++ 特性的臃腫. C++ 是一門包含大量高級特性的龐大語言. 某些狀況下, 咱們會限制甚至禁止使用某些特性. 這麼作是爲了保持代碼清爽, 避免這些特性可能致使的各類問題. 指南中列舉了這類特性, 並解釋爲何這些特性被限制使用.
Google 主導的開源項目均符合本指南的規定.
注意: 本指南並不是 C++ 教程, 咱們假定讀者已經對 C++ 很是熟悉.
一般每個 .cc 文件都有一個對應的 .h 文件. 也有一些常見例外, 如單元測試代碼和只包含 main() 函數的 .cc 文件.
正確使用頭文件可令代碼在可讀性、文件大小和性能上大爲改觀.
下面的規則將引導你規避使用頭文件時的各類陷阱.
Tip
全部頭文件都應該使用 #define 防止頭文件被多重包含, 命名格式當是: <PROJECT>_<PATH>_<FILE>_H_
爲保證惟一性, 頭文件的命名應該依據所在項目源代碼樹的全路徑. 例如, 項目 foo 中的頭文件 foo/src/bar/baz.h 可按以下方式保護:
#ifndef FOO_BAR_BAZ_H_ #define FOO_BAR_BAZ_H_ … #endif // FOO_BAR_BAZ_H_
Tip
能用前置聲明的地方儘可能不使用 #include.
當一個頭文件被包含的同時也引入了新的依賴, 一旦該頭文件被修改, 代碼就會被從新編譯. 若是這個頭文件又包含了其餘頭文件, 這些頭文件的任何改變都將致使全部包含了該頭文件的代碼被從新編譯. 所以, 咱們傾向於減小包含頭文件, 尤爲是在頭文件中包含頭文件.
使用前置聲明能夠顯著減小須要包含的頭文件數量. 舉例說明: 若是頭文件中用到類 File, 但不須要訪問 File 類的聲明, 頭文件中只需前置聲明 class File; 而無須 #include"file/base/file.h".
不容許訪問類的定義的前提下, 咱們在一個頭文件中能對類 Foo 作哪些操做?
反之, 若是你的類是 Foo 的子類, 或者含有類型爲 Foo 的非靜態數據成員, 則必須包含 Foo 所在的頭文件.
有時, 使用指針成員 (若是是 scoped_ptr 更好) 替代對象成員的確是明智之選. 然而, 這會下降代碼可讀性及執行效率, 所以若是僅僅爲了少包含頭文件,仍是不要這麼作的好.
固然 .cc 文件不管如何都須要所使用類的定義部分, 天然也就會包含若干頭文件.
Tip
只有當函數只有 10 行甚至更少時纔將其定義爲內聯函數.
定義:
當函數被聲明爲內聯函數以後, 編譯器會將其內聯展開, 而不是按一般的函數調用機制進行調用.
優勢:
當函數體比較小的時候, 內聯該函數能夠令目標代碼更加高效. 對於存取函數以及其它函數體比較短, 性能關鍵的函數, 鼓勵使用內聯.
缺點:
濫用內聯將致使程序變慢. 內聯可能使目標代碼量或增或減, 這取決於內聯函數的大小. 內聯很是短小的存取函數一般會減小代碼大小, 但內聯一個至關大的函數將戲劇性的增長代碼大小. 現代處理器因爲更好的利用了指令緩存, 小巧的代碼每每執行更快。
結論:
一個較爲合理的經驗準則是, 不要內聯超過 10 行的函數. 謹慎對待析構函數, 析構函數每每比其表面看起來要更長, 由於有隱含的成員和基類析構函數被調用!
另外一個實用的經驗準則: 內聯那些包含循環或 switch 語句的函數經常是得不償失 (除非在大多數狀況下, 這些循環或 switch 語句從不被執行).
有些函數即便聲明爲內聯的也不必定會被編譯器內聯, 這點很重要; 好比虛函數和遞歸函數就不會被正常內聯. 一般, 遞歸函數不該該聲明成內聯函數.(YuleFox 注: 遞歸調用堆棧的展開並不像循環那麼簡單, 好比遞歸層數在編譯時多是未知的, 大多數編譯器都不支持內聯遞歸函數). 虛函數內聯的主要緣由則是想把它的函數體放在類定義內, 爲了圖個方便, 抑或是看成文檔描述其行爲, 好比精短的存取函數.
Tip
複雜的內聯函數的定義, 應放在後綴名爲 -inl.h 的頭文件中.
內聯函數的定義必須放在頭文件中, 編譯器才能在調用點內聯展開定義. 然而, 實現代碼理論上應該放在 .cc 文件中, 咱們不但願 .h 文件中有太多實現代碼, 除非在可讀性和性能上有明顯優點.
若是內聯函數的定義比較短小, 邏輯比較簡單, 實現代碼放在 .h 文件裏沒有任何問題. 好比, 存取函數的實現理所固然都應該放在類定義內. 出於編寫者和調用者的方便, 較複雜的內聯函數也能夠放到 .h 文件中, 若是你以爲這樣會使頭文件顯得笨重, 也能夠把它萃取到單獨的 -inl.h 中. 這樣把實現和類定義分離開來, 當須要時包含對應的 -inl.h 便可。
-inl.h 文件還可用於函數模板的定義. 從而加強模板定義的可讀性.
別忘了 -inl.h 和其餘頭文件同樣, 也須要 #define 保護.
Tip
定義函數時, 參數順序依次爲: 輸入參數, 而後是輸出參數.
C/C++ 函數參數分爲輸入參數, 輸出參數, 和輸入/輸出參數三種. 輸入參數通常傳值或傳 const 引用, 輸出參數或輸入/輸出參數則是非-const 指針. 對參數排序時, 將只輸入的參數放在全部輸出參數以前. 尤爲是不要僅僅由於是新加的參數, 就把它放在最後; 即便是新加的只輸入參數也要放在輸出參數.
這條規則並不須要嚴格遵照. 輸入/輸出兩用參數 (一般是類/結構體變量) 把事情變得複雜, 爲保持和相關函數的一致性, 你有時不得不有所變通.
Tip
使用標準的頭文件包含順序可加強可讀性, 避免隱藏依賴: C 庫, C++ 庫, 其餘庫的 .h, 本項目內的 .h.
項目內頭文件應按照項目源代碼目錄樹結構排列, 避免使用 UNIX 特殊的快捷目錄 . (當前目錄) 或 .. (上級目錄). 例如, google-awesome-project/src/base/logging.h 應該按以下方式包含:
#include 「base/logging.h」
又如, dir/foo.cc 的主要做用是實現或測試 dir2/foo2.h 的功能, foo.cc 中包含頭文件的次序以下:
這種排序方式可有效減小隱藏依賴. 咱們但願每個頭文件都是可被獨立編譯的 (yospaly 譯註: 即該頭文件自己已包含全部必要的顯式依賴), 最簡單的方法是將其做爲第一個 .h 文件#included 進對應的 .cc.
dir/foo.cc 和 dir2/foo2.h 一般位於同一目錄下 (如 base/basictypes_unittest.cc 和 base/basictypes.h), 但也能夠放在不一樣目錄下.
按字母順序對頭文件包含進行二次排序是不錯的主意 (yospaly 譯註: 以前已經按頭文件類別排過序了).
舉例來講, google-awesome-project/src/foo/internal/fooserver.cc 的包含次序以下:
#include "foo/public/fooserver.h" // 優先位置 #include <sys/types.h> #include <unistd.h> #include <hash_map> #include <vector> #include "base/basictypes.h" #include "base/commandlineflags.h" #include "foo/public/bar.h"
Tip
鼓勵在 .cc 文件內使用匿名名字空間. 使用具名的名字空間時, 其名稱可基於項目名或相對路徑. 不要使用 using 關鍵字.
定義:
名字空間將全局做用域細分爲獨立的, 具名的做用域, 可有效防止全局做用域的命名衝突.
優勢:
雖然類已經提供了(可嵌套的)命名軸線 (YuleFox 注: 將命名分割在不一樣類的做用域內), 名字空間在這基礎上又封裝了一層.
舉例來講, 兩個不一樣項目的全局做用域都有一個類 Foo, 這樣在編譯或運行時形成衝突. 若是每一個項目將代碼置於不一樣名字空間中, project1::Foo 和 project2::Foo 做爲不一樣符號天然不會衝突.
缺點:
名字空間具備迷惑性, 由於它們和類同樣提供了額外的 (可嵌套的) 命名軸線.
在頭文件中使用匿名空間致使違背 C++ 的惟必定義原則 (One Definition Rule (ODR)).
結論:
根據下文將要提到的策略合理使用命名空間.
在 .cc 文件中, 容許甚至鼓勵使用匿名名字空間, 以免運行時的命名衝突:
namespace { // .cc 文件中 // 名字空間的內容無需縮進 enum { kUNUSED, kEOF, kERROR }; // 常用的符號 bool AtEof() { return pos_ == kEOF; } // 使用本名字空間內的符號 EOF } // namespace
然而, 與特定類關聯的文件做用域聲明在該類中被聲明爲類型, 靜態數據成員或靜態成員函數, 而不是匿名名字空間的成員. 如上例所示, 匿名空間結束時用註釋 // namespace標識.
不要在 .h 文件中使用匿名名字空間.
具名的名字空間使用方式以下:
用名字空間把文件包含, gflags 的聲明/定義, 以及類的前置聲明之外的整個源文件封裝起來, 以區別於其它名字空間:
// .h 文件 namespace mynamespace { // 全部聲明都置於命名空間中 // 注意不要使用縮進 class MyClass { public: … void Foo(); }; } // namespace mynamespace// .cc 文件 namespace mynamespace { // 函數定義都置於命名空間中 void MyClass::Foo() { … } } // namespace mynamespace一般的 .cc 文件包含更多, 更復雜的細節, 好比引用其餘名字空間的類等.
#include 「a.h」 DEFINE_bool(someflag, false, 「dummy flag」); class C; // 全局名字空間中類 C 的前置聲明 namespace a { class A; } // a::A 的前置聲明 namespace b { …code for b… // b 中的代碼 } // namespace b不要在名字空間 std 內聲明任何東西, 包括標準庫的類前置聲明. 在 std 名字空間聲明實體會致使不肯定的問題, 好比不可移植. 聲明標準庫下的實體, 須要包含對應的頭文件.
最好不要使用 ``using`` 關鍵字, 以保證名字空間下的全部名稱均可以正常使用.
// 禁止 —— 污染名字空間 using namespace foo;在 .cc 文件, .h 文件的函數, 方法或類中, 能夠使用 ``using`` 關鍵字.
// 容許: .cc 文件中 // .h 文件的話, 必須在函數, 方法或類的內部使用 using ::foo::bar;在 .cc 文件, .h 文件的函數, 方法或類中, 容許使用名字空間別名.
// 容許: .cc 文件中 // .h 文件的話, 必須在函數, 方法或類的內部使用 namespace fbz = ::foo::bar::baz;
Tip
當公有嵌套類做爲接口的一部分時, 雖然能夠直接將他們保持在全局做用域中, 但將嵌套類的聲明置於名字空間內是更好的選擇.
定義: 在一個類內部定義另外一個類; 嵌套類也被稱爲 成員類 (member class).
class Foo { private: // Bar是嵌套在Foo中的成員類 class Bar { … }; };
優勢:
當嵌套 (或成員) 類只被外圍類使用時很是有用; 把它做爲外圍類做用域內的成員, 而不是去污染外部做用域的同名類. 嵌套類能夠在外圍類中作前置聲明, 而後在 .cc 文件中定義, 這樣避免在外圍類的聲明中定義嵌套類, 由於嵌套類的定義一般只與實現相關.
缺點:
嵌套類只能在外圍類的內部作前置聲明. 所以, 任何使用了 Foo::Bar* 指針的頭文件不得不包含類 Foo 的整個聲明.
結論:
不要將嵌套類定義成公有, 除非它們是接口的一部分, 好比, 嵌套類含有某些方法的一組選項.
Tip
使用靜態成員函數或名字空間內的非成員函數, 儘可能不要用裸的全局函數.
優勢:
某些狀況下, 非成員函數和靜態成員函數是很是有用的, 將非成員函數放在名字空間內可避免污染全局做用域.
缺點:
將非成員函數和靜態成員函數做爲新類的成員或許更有意義, 當它們須要訪問外部資源或具備重要的依賴關係時更是如此.
結論:
有時, 把函數的定義同類的實例脫鉤是有益的, 甚至是必要的. 這樣的函數能夠被定義成靜態成員, 或是非成員函數. 非成員函數不該依賴於外部變量, 應儘可能置於某個名字空間內. 相比單純爲了封裝若干不共享任何靜態數據的靜態成員函數而建立類, 不如使用命名空間.
定義在同一編譯單元的函數, 被其餘編譯單元直接調用可能會引入沒必要要的耦合和連接時依賴; 靜態成員函數對此尤爲敏感. 能夠考慮提取到新類中, 或者將函數置於獨立庫的名字空間內.
若是你必須定義非成員函數, 又只是在 .cc 文件中使用它, 可以使用匿名名字空間或 static 連接關鍵字 (如 static int Foo() {...}) 限定其做用域.
Tip
將函數變量儘量置於最小做用域內, 並在變量聲明時進行初始化.
C++ 容許在函數的任何位置聲明變量. 咱們提倡在儘量小的做用域中聲明變量, 離第一次使用越近越好. 這使得代碼瀏覽者更容易定位變量聲明的位置, 瞭解變量的類型和初始值. 特別是,應使用初始化的方式替代聲明再賦值, 好比:
int i; i = f(); // 壞——初始化和聲明分離 nt j = g(); // 好——初始化時聲明
注意, GCC 可正確實現了 for (int i = 0; i < 10; ++i) (i 的做用域僅限 for 循環內), 因此其餘 for 循環中能夠從新使用 i. 在 if 和 while 等語句中的做用域聲明也是正確的, 如:
while (const char* p = strchr(str, ‘/’)) str = p + 1;
Warning
若是變量是一個對象, 每次進入做用域都要調用其構造函數, 每次退出做用域都要調用其析構函數.
// 低效的實現 for (int i = 0; i < 1000000; ++i) { Foo f; // 構造函數和析構函數分別調用 1000000 次! f.DoSomething(i); }
在循環做用域外面聲明這類變量要高效的多:
Foo f; // 構造函數和析構函數只調用 1 次 for (int i = 0; i < 1000000; ++i) { f.DoSomething(i); }
Tip
禁止使用 class 類型的靜態或全局變量: 它們會致使很難發現的 bug 和不肯定的構造和析構函數調用順序.
靜態生存週期的對象, 包括全局變量, 靜態變量, 靜態類成員變量, 以及函數靜態變量, 都必須是原生數據類型 (POD : Plain Old Data): 只能是 int, char, float, 和 void, 以及 POD 類型的數組/結構體/指針. 永遠不要使用函數返回值初始化靜態變量; 不要在多線程代碼中使用非 const 的靜態變量.
不幸的是, 靜態變量的構造函數, 析構函數以及初始化操做的調用順序在 C++ 標準中未明肯定義, 甚至每次編譯構建都有可能會發生變化, 從而致使難以發現的 bug. 好比, 結束程序時, 某個靜態變量已經被析構了, 但代碼還在跑 – 其它線程極可能 – 試圖訪問該變量, 直接致使崩潰.
因此, 咱們只容許 POD 類型的靜態變量. 本條規則徹底禁止 vector (使用 C 數組替代), string (使用 const char*), 及其它以任意方式包含或指向類實例的東東, 成爲靜態變量. 出於一樣的理由, 咱們不容許用函數返回值來初始化靜態變量.
若是你確實須要一個 class` 類型的靜態或全局變量, 能夠考慮在 ``main() 函數或 pthread_once() 內初始化一個你永遠不會回收的指針.
Note
yospaly 譯註:
上文說起的靜態變量泛指靜態生存週期的對象, 包括: 全局變量, 靜態變量, 靜態類成員變量, 以及函數靜態變量.
類是 C++ 中代碼的基本單元. 顯然, 它們被普遍使用. 本節列舉了在寫一個類時的主要注意事項.
Tip
構造函數中只進行那些沒什麼意義的 (trivial, YuleFox 注: 簡單初始化對於程序執行沒有實際的邏輯意義, 由於成員變量 「有意義」 的值大多不在構造函數中肯定) 初始化, 可能的話, 使用Init() 方法集中初始化有意義的 (non-trivial) 數據.
定義:
在構造函數體中進行初始化操做.
優勢:
排版方便, 無需擔憂類是否已經初始化.
缺點:
在構造函數中執行操做引發的問題有:
結論:
若是對象須要進行有意義的 (non-trivial) 初始化, 考慮使用明確的 Init() 方法並 (或) 增長一個成員標記用於指示對象是否已經初始化成功.
Tip
若是一個類定義了若干成員變量又沒有其它構造函數, 必須定義一個默認構造函數. 不然編譯器將自動生產一個很糟糕的默認構造函數.
定義:
new 一個不帶參數的類對象時, 會調用這個類的默認構造函數. 用 new[] 建立數組時,默認構造函數則老是被調用.
優勢:
默認將結構體初始化爲 「無效」 值, 使調試更方便.
缺點:
對代碼編寫者來講, 這是多餘的工做.
結論:
若是類中定義了成員變量, 並且沒有提供其它構造函數, 你必須定義一個 (不帶參數的) 默認構造函數. 把對象的內部狀態初始化成一致/有效的值無疑是更合理的方式.
這麼作的緣由是: 若是你沒有提供其它構造函數, 又沒有定義默認構造函數, 編譯器將爲你自動生成一個. 編譯器生成的構造函數並不會對對象進行合理的初始化.
若是你定義的類繼承現有類, 而你又沒有增長新的成員變量, 則不須要爲新類定義默認構造函數.
Tip
對單個參數的構造函數使用 C++ 關鍵字 explicit.
定義:
一般, 若是構造函數只有一個參數, 可當作是一種隱式轉換. 打個比方, 若是你定義了 Foo::Foo(string name), 接着把一個字符串傳給一個以 Foo 對象爲參數的函數, 構造函數Foo::Foo(string name) 將被調用, 並將該字符串轉換爲一個 Foo 的臨時對象傳給調用函數. 看上去很方便, 但若是你並不但願如此經過轉換生成一個新對象的話, 麻煩也隨之而來. 爲避免構造函數被調用形成隱式轉換, 能夠將其聲明爲 explicit.
優勢:
避免不合時宜的變換.
缺點:
無
結論:
全部單參數構造函數都必須是顯式的. 在類定義中, 將關鍵字 explicit 加到單參數構造函數前: explicit Foo(string name);
例外: 在極少數狀況下, 拷貝構造函數能夠不聲明成 explicit. 做爲其它類的透明包裝器的類也是特例之一. 相似的例外狀況應在註釋中明確說明.
Tip
僅在代碼中須要拷貝一個類對象的時候使用拷貝構造函數; 大部分狀況下都不須要, 此時應使用 DISALLOW_COPY_AND_ASSIGN.
定義:
拷貝構造函數在複製一個對象到新建對象時被調用 (特別是對象傳值時).
優勢:
拷貝構造函數使得拷貝對象更加容易. STL 容器要求全部內容可拷貝, 可賦值.
缺點:
C++ 中的隱式對象拷貝是不少性能問題和 bug 的根源. 拷貝構造函數下降了代碼可讀性, 相比傳引用, 跟蹤傳值的對象更加困難, 對象修改的地方變得難以捉摸.
結論:
大部分類並不須要可拷貝, 也不須要一個拷貝構造函數或重載賦值運算符. 不幸的是, 若是你不主動聲明它們, 編譯器會爲你自動生成, 並且是 public 的.
能夠考慮在類的 private: 中添加拷貝構造函數和賦值操做的空實現, 只有聲明, 沒有定義. 因爲這些空函數聲明爲 private, 當其餘代碼試圖使用它們的時候, 編譯器將報錯. 方便起見, 咱們能夠使用 DISALLOW_COPY_AND_ASSIGN 宏:
// 禁止使用拷貝構造函數和 operator= 賦值操做的宏 // 應該類的 private: 中使用 #define DISALLOW_COPY_AND_ASSIGN(TypeName) \ TypeName(const TypeName&); \ void operator=(const TypeName&)
在 class foo: 中:
class Foo { public: Foo(int f); ~Foo(); private: DISALLOW_COPY_AND_ASSIGN(Foo); };
如上所述, 絕大多數狀況下都應使用 DISALLOW_COPY_AND_ASSIGN 宏. 若是類確實須要可拷貝, 應在該類的頭文件中說明起因, 併合理的定義拷貝構造函數和賦值操做. 注意在operator= 中檢測自我賦值的狀況 (yospaly 注: 即 operator= 接收的參數是該對象自己).
爲了能做爲 STL 容器的值, 你可能有使類可拷貝的衝動. 在大多數相似的狀況下, 真正該作的是把對象的 指針 放到 STL 容器中. 能夠考慮使用 std::tr1::shared_ptr.
Tip
僅當只有數據時使用 struct, 其它一律使用 class.
在 C++ 中 struct 和 class 關鍵字幾乎含義同樣. 咱們爲這兩個關鍵字添加咱們本身的語義理解, 以便爲定義的數據類型選擇合適的關鍵字.
struct 用來定義包含數據的被動式對象, 也能夠包含相關的常量, 但除了存取數據成員以外, 沒有別的函數功能. 而且存取功能是經過直接訪問位域 (field), 而非函數調用. 除了構造函數, 析構函數, Initialize(), Reset(), Validate() 外, 不能提供其它功能的函數.
若是須要更多的函數功能, class 更適合. 若是拿不許, 就用 class.
爲了和 STL 保持一致, 對於仿函數 (functors) 和特性 (traits) 能夠不用 class 而是使用 struct.
注意: 類和結構體的成員變量使用 不一樣的命名規則.
Tip
使用組合 (composition, YuleFox 注: 這一點也是 GoF 在 <<Design Patterns>> 裏反覆強調的) 經常比使用繼承更合理. 若是使用繼承的話, 定義爲 public 繼承.
定義:
當子類繼承基類時, 子類包含了父基類全部數據及操做的定義. C++ 實踐中, 繼承主要用於兩種場合: 實現繼承 (implementation inheritance), 子類繼承父類的實現代碼; 接口繼承 (interface inheritance), 子類僅繼承父類的方法名稱.
優勢:
實現繼承經過原封不動的複用基類代碼減小了代碼量. 因爲繼承是在編譯時聲明, 程序員和編譯器均可以理解相應操做並發現錯誤. 從編程角度而言, 接口繼承是用來強制類輸出特定的 API. 在類沒有實現 API 中某個必須的方法時, 編譯器一樣會發現並報告錯誤.
缺點:
對於實現繼承, 因爲子類的實現代碼散佈在父類和子類間之間, 要理解其實現變得更加困難. 子類不能重寫父類的非虛函數, 固然也就不能修改其實現. 基類也可能定義了一些數據成員, 還要區分基類的實際佈局.
結論:
全部繼承必須是 public 的. 若是你想使用私有繼承, 你應該替換成把基類的實例做爲成員對象的方式.
不要過分使用實現繼承. 組合經常更合適一些. 儘可能作到只在 「是一個」 (「is-a」, YuleFox 注: 其餘 「has-a」 狀況下請使用組合) 的狀況下使用繼承: 若是 Bar 的確 「是一種」 Foo, Bar才能繼承 Foo.
必要的話, 析構函數聲明爲 virtual. 若是你的類有虛函數, 則析構函數也應該爲虛函數. 注意 數據成員在任何狀況下都必須是私有的.
當重載一個虛函數, 在衍生類中把它明確的聲明爲 virtual. 理論依據: 若是省略 virtual 關鍵字, 代碼閱讀者不得不檢查全部父類, 以判斷該函數是不是虛函數.
Tip
真正須要用到多重實現繼承的狀況少之又少. 只在如下狀況咱們才容許多重繼承: 最多隻有一個基類是非抽象類; 其它基類都是以 Interface 爲後綴的 純接口類.
定義:
多重繼承容許子類擁有多個基類. 要將做爲 純接口 的基類和具備 實現 的基類區別開來.
優勢:
相比單繼承 (見 繼承), 多重實現繼承能夠複用更多的代碼.
缺點:
真正須要用到多重 實現 繼承的狀況少之又少. 多重實現繼承看上去是不錯的解決方案, 但你一般也能夠找到一個更明確, 更清晰的不一樣解決方案.
結論:
只有當全部父類除第一個外都是 純接口類 時, 才容許使用多重繼承. 爲確保它們是純接口, 這些類必須以 Interface 爲後綴.
Note
關於該規則, Windows 下有個 特例.
Tip
接口是指知足特定條件的類, 這些類以 Interface 爲後綴 (不強制).
定義:
當一個類知足如下要求時, 稱之爲純接口:
- 只有純虛函數 (「=0「) 和靜態函數 (除了下文提到的析構函數).
- 沒有非靜態數據成員.
- 沒有定義任何構造函數. 若是有, 也不能帶有參數, 而且必須爲 protected.
- 若是它是一個子類, 也只能從知足上述條件並以 Interface 爲後綴的類繼承.
接口類不能被直接實例化, 由於它聲明瞭純虛函數. 爲確保接口類的全部實現可被正確銷燬, 必須爲之聲明虛析構函數 (做爲上述第 1 條規則的特例, 析構函數不能是純虛函數). 具體細節可參考 Stroustrup 的 The C++ Programming Language, 3rd edition 第 12.4 節.
優勢:
以 Interface 爲後綴能夠提醒其餘人不要爲該接口類增長函數實現或非靜態數據成員. 這一點對於 多重繼承 尤爲重要. 另外, 對於 Java 程序員來講, 接口的概念已經是深刻人心.
缺點:
Interface 後綴增長了類名長度, 爲閱讀和理解帶來不便. 同時,接口特性做爲實現細節不該暴露給用戶.
結論:
只有在知足上述須要時, 類才以 Interface 結尾, 但反過來, 知足上述須要的類未必必定以 Interface 結尾.
Tip
除少數特定環境外,不要重載運算符.
定義:
一個類能夠定義諸如 + 和 / 等運算符, 使其能夠像內建類型同樣直接操做.
優勢:
使代碼看上去更加直觀, 類表現的和內建類型 (如 int) 行爲一致. 重載運算符使 Equals(), Add() 等函數名黯然失色. 爲了使一些模板函數正確工做, 你可能必須定義操做符.
缺點:
雖然操做符重載令代碼更加直觀, 但也有一些不足:
重載還有令你吃驚的反作用. 好比, 重載了 operator& 的類不能被前置聲明.
結論:
通常不要重載運算符. 尤爲是賦值操做 (operator=) 比較詭異, 應避免重載. 若是須要的話, 能夠定義相似 Equals(), CopyFrom() 等函數.
然而, 極少數狀況下可能須要重載運算符以便與模板或 「標準」 C++ 類互操做 (如 operator<<(ostream&, const T&)). 只有被證實是徹底合理的才能重載, 但你仍是要儘量避免這樣作. 尤爲是不要僅僅爲了在 STL 容器中用做鍵值就重載 operator== 或 operator<; 相反, 你應該在聲明容器的時候, 建立相等判斷和大小比較的仿函數類型.
有些 STL 算法確實須要重載 operator== 時, 你能夠這麼作, 記得別忘了在文檔中說明緣由.
Tip
將 全部 數據成員聲明爲 private, 並根據須要提供相應的存取函數. 例如, 某個名爲 foo_ 的變量, 其取值函數是 foo(). 還可能須要一個賦值函數 set_foo().
通常在頭文件中把存取函數定義成內聯函數.
Tip
在類中使用特定的聲明順序: public: 在 private: 以前, 成員函數在數據成員 (變量) 前;
類的訪問控制區段的聲明順序依次爲: public:, protected:, private:. 若是某區段沒內容, 能夠不聲明.
每一個區段內的聲明一般按如下順序:
- typedefs 和枚舉
- 常量
- 構造函數
- 析構函數
- 成員函數, 含靜態成員函數
- 數據成員, 含靜態數據成員
宏 DISALLOW_COPY_AND_ASSIGN 的調用放在 private: 區段的末尾. 它一般是類的最後部分. 參考 拷貝構造函數.
.cc 文件中函數的定義應儘量和聲明順序一致.
不要在類定義中內聯大型函數. 一般, 只有那些沒有特別意義或性能要求高, 而且是比較短小的函數才能被定義爲內聯函數. 更多細節參考 內聯函數.
Tip
傾向編寫簡短, 凝練的函數.
咱們認可長函數有時是合理的, 所以並不硬性限制函數的長度. 若是函數超過 40 行, 能夠思索一下能不能在不影響程序結構的前提下對其進行分割.
即便一個長函數如今工做的很是好, 一旦有人對其修改, 有可能出現新的問題. 甚至致使難以發現的 bug. 使函數儘可能簡短, 便於他人閱讀和修改代碼.
在處理代碼時, 你可能會發現複雜的長函數. 不要懼怕修改現有代碼: 若是證明這些代碼使用 / 調試困難, 或者你須要使用其中的一小段代碼, 考慮將其分割爲更加簡短並易於管理的若干函數.
Google 用了不少本身實現的技巧 / 工具使 C++ 代碼更加健壯, 咱們使用 C++ 的方式可能和你在其它地方見到的有所不一樣.
Tip
若是確實須要使用智能指針的話, scoped_ptr 徹底能夠勝任. 你應該只在很是特定的狀況下使用 std::tr1::shared_ptr, 例如 STL 容器中的對象. 任何狀況下都不要使用 auto_ptr.
「智能」 指針看上去是指針, 實際上是附加了語義的對象. 以 scoped_ptr 爲例, scoped_ptr 被銷燬時, 它會刪除所指向的對象. shared_ptr 也是如此, 而且 shared_ptr 實現了引用計數, 因此最後一個 shared_ptr 對象析構時, 若是檢測到引用次數爲 0,就會銷燬所指向的對象.
通常來講,咱們傾向於設計對象隸屬明確的代碼, 最明確的對象隸屬是根本不使用指針, 直接將對象做爲一個做用域或局部變量使用. 另外一種極端作法是, 引用計數指針不屬於任何對象. 這種方法的問題是容易致使循環引用, 或者致使某個對象沒法刪除的詭異狀態, 並且在每一次拷貝或賦值時連原子操做都會很慢.
雖然不推薦使用引用計數指針, 但有些時候它們的確是最簡單有效的解決方案.
(YuleFox 注: 看來, Google 所謂的不一樣之處, 在於儘可能避免使用智能指針 :D, 使用時也儘可能局部化, 而且, 安全第一)
Tip
使用 cpplint.py 檢查風格錯誤.
cpplint.py 是一個用來分析源文件, 能檢查出多種風格錯誤的工具. 它不併完美, 甚至還會漏報和誤報, 但它仍然是一個很是有用的工具. 用行註釋 // NOLINT 能夠忽略誤報.
某些項目會指導你如何使用他們的項目工具運行 cpplint.py. 若是你參與的項目沒有提供, 你能夠單獨下載 cpplint.py.
Tip
因此按引用傳遞的參數必須加上 const.
定義:
在 C 語言中, 若是函數須要修改變量的值, 參數必須爲指針, 如 int foo(int *pval). 在 C++ 中, 函數還能夠聲明引用參數: int foo(int &val).
優勢:
定義引用參數防止出現 (*pval)++ 這樣醜陋的代碼. 像拷貝構造函數這樣的應用也是必需的. 並且更明確, 不接受 NULL 指針.
缺點:
容易引發誤解, 由於引用在語法上是值變量卻擁有指針的語義.
結論:
函數參數列表中, 全部引用參數都必須是 const:
void Foo(const string &in, string *out);
事實上這在 Google Code 是一個硬性約定: 輸入參數是值參或 const 引用, 輸出參數爲指針. 輸入參數能夠是 const 指針, 但決不能是 非 const 的引用參數.
在如下狀況你能夠把輸入參數定義爲 const 指針: 你想強調參數不是拷貝而來的, 在對象生存週期內必須一直存在; 最好同時在註釋中詳細說明一下. bind2nd 和 mem_fun 等 STL 適配器不接受引用參數, 這種狀況下你也必須把函數參數聲明成指針類型.
Tip
僅在輸入參數類型不一樣, 功能相同時使用重載函數 (含構造函數). 不要用函數重載模擬 缺省函數參數.
定義:
你能夠編寫一個參數類型爲 const string& 的函數, 而後用另外一個參數類型爲 const char* 的函數重載它:
class MyClass { public: void Analyze(const string &text); void Analyze(const char *text, size_t textlen); };
優勢:
經過重載參數不一樣的同名函數, 令代碼更加直觀. 模板化代碼須要重載, 同時爲使用者帶來便利.
缺點:
限制使用重載的一個緣由是在某個特定調用點很難肯定到底調用的是哪一個函數. 另外一個緣由是當派生類只重載了某個函數的部分變體, 會令不少人對繼承的語義產生困惑. 此外在閱讀庫的用戶代碼時, 可能會因反對使用 缺省函數參數 形成沒必要要的費解.
結論:
若是你想重載一個函數, 考慮讓函數名包含參數信息, 例如, 使用 AppendString(), AppendInt() 而不是 Append().
Tip
咱們不容許使用缺省函數參數.
優勢:
多數狀況下, 你寫的函數可能會用到不少的缺省值, 但偶爾你也會修改這些缺省值. 無須爲了這些偶爾狀況定義不少的函數, 用缺省參數就能很輕鬆的作到這點.
缺點:
你們一般都是經過查看別人的代碼來推斷如何使用 API. 用了缺省參數的代碼更難維護, 從老代碼複製粘貼而來的新代碼可能只包含部分參數. 當缺省參數不適用於新代碼時可能會致使重大問題.
結論:
咱們規定全部參數必須明確指定, 迫使程序員理解 API 和各參數值的意義, 避免默默使用他們可能都還沒意識到的缺省參數.
Tip
咱們不容許使用變長數組和 alloca().
優勢:
變長數組具備渾然天成的語法. 變長數組和 alloca() 也都很高效.
缺點:
變長數組和 alloca() 不是標準 C++ 的組成部分. 更重要的是, 它們根據數據大小動態分配堆棧內存, 會引發難以發現的內存越界 bugs: 「在個人機器上運行的好好的, 發佈後卻莫名其妙的掛掉了」.
結論:
使用安全的內存分配器, 如 scoped_ptr / scoped_array.
Tip
咱們容許合理的使用友元類及友元函數.
一般友元應該定義在同一文件內, 避免代碼讀者跑到其它文件查找使用該私有成員的類. 常常用到友元的一個地方是將 FooBuilder 聲明爲 Foo 的友元, 以便 FooBuilder 正確構造 Foo 的內部狀態, 而無需將該狀態暴露出來. 某些狀況下, 將一個單元測試類聲明成待測類的友元會很方便.
友元擴大了 (但沒有打破) 類的封裝邊界. 某些狀況下, 相對於將類成員聲明爲 public, 使用友元是更好的選擇, 尤爲是若是你只容許另外一個類訪問該類的私有成員時. 固然, 大多數類都只應該經過其提供的公有成員進行互操做.
Tip
咱們不使用 C++ 異常.
優勢:
缺點:
結論:
從表面上看, 使用異常利大於弊, 尤爲是在新項目中. 可是對於現有代碼, 引入異常會牽連到全部相關代碼. 若是新項目容許異常向外擴散, 在跟之前未使用異常的代碼整合時也將是個麻煩. 由於 Google 現有的大多數 C++ 代碼都沒有異常處理, 引入帶有異常處理的新代碼至關困難.
鑑於 Google 現有代碼不接受異常, 在現有代碼中使用異常比在新項目中使用的代價多少要大一些. 遷移過程比較慢, 也容易出錯. 咱們不相信異常的使用有效替代方案, 如錯誤代碼, 斷言等會形成嚴重負擔.
咱們並非基於哲學或道德層面反對使用異常, 而是在實踐的基礎上. 咱們但願在 Google 使用咱們本身的開源項目, 但項目中使用異常會爲此帶來不便, 所以咱們也建議不要在 Google 的開源項目中使用異常. 若是咱們須要把這些項目推倒重來顯然不太現實.
對於 Windows 代碼來講, 有個 特例.
(YuleFox 注: 對於異常處理, 顯然不是短短几句話可以說清楚的, 以構造函數爲例, 不少 C++ 書籍上都提到當構造失敗時只有異常能夠處理, Google 禁止使用異常這一點, 僅僅是爲了自身的方便, 說大了, 無非是基於軟件管理成本上, 實際使用中仍是本身決定)
Tip
咱們禁止使用 RTTI.
定義:
RTTI 容許程序員在運行時識別 C++ 類對象的類型.
優勢:
RTTI 在某些單元測試中很是有用. 好比進行工廠類測試時, 用來驗證一個新建對象是否爲指望的動態類型.
除測試外, 極少用到.
缺點:
在運行時判斷類型一般意味着設計問題. 若是你須要在運行期間肯定一個對象的類型, 這一般說明你須要考慮從新設計你的類.
結論:
除單元測試外, 不要使用 RTTI. 若是你發現本身不得不寫一些行爲邏輯取決於對象類型的代碼, 考慮換一種方式判斷對象類型.
若是要實現根據子類類型來肯定執行不一樣邏輯代碼, 虛函數無疑更合適. 在對象內部就能夠處理類型識別問題.
若是要在對象外部的代碼中判斷類型, 考慮使用雙重分派方案, 如訪問者模式. 能夠方便的在對象自己以外肯定類的類型.
若是你認爲上面的方法你真的掌握不了, 你能夠使用 RTTI, 但務必請三思 :-) . 不要試圖手工實現一個貌似 RTTI 的替代方案, 咱們反對使用 RTTI 的理由, 一樣適用於那些在類型繼承體系上使用類型標籤的替代方案.
Tip
使用 C++ 的類型轉換, 如 static_cast<>(). 不要使用 int y = (int)x 或 int y = int(x) 等轉換方式;
定義:
C++ 採用了有別於 C 的類型轉換機制, 對轉換操做進行歸類.
優勢:
C 語言的類型轉換問題在於模棱兩可的操做; 有時是在作強制轉換 (如 (int)3.5), 有時是在作類型轉換 (如 (int)"hello"). 另外, C++ 的類型轉換在查找時更醒目.
缺點:
噁心的語法.
結論:
不要使用 C 風格類型轉換. 而應該使用 C++ 風格.
- 用 static_cast 替代 C 風格的值轉換, 或某個類指針須要明確的向上轉換爲父類指針時.
- 用 const_cast 去掉 const 限定符.
- 用 reinterpret_cast 指針類型和整型或其它指針之間進行不安全的相互轉換. 僅在你對所作一切瞭然於心時使用.
- dynamic_cast 測試代碼之外不要使用. 除非是單元測試, 若是你須要在運行時肯定類型信息, 說明有 設計缺陷.
Tip
只在記錄日誌時使用流.
定義:
流用來替代 printf() 和 scanf().
優勢:
有了流, 在打印時不須要關心對象的類型. 不用擔憂格式化字符串與參數列表不匹配 (雖然在 gcc 中使用 printf 也不存在這個問題). 流的構造和析構函數會自動打開和關閉對應的文件.
缺點:
流使得 pread() 等功能函數很難執行. 若是不使用 printf 風格的格式化字符串, 某些格式化操做 (尤爲是經常使用的格式字符串 %.*s) 用流處理性能是很低的. 流不支持字符串操做符從新排序 (%1s), 而這一點對於軟件國際化頗有用.
結論:
不要使用流, 除非是日誌接口須要. 使用 printf 之類的代替.
使用流還有不少利弊, 但代碼一致性賽過一切. 不要在代碼中使用流.
拓展討論:
對這一條規則存在一些爭論, 這兒給出點深層次緣由. 回想一下惟一性原則 (Only One Way): 咱們但願在任什麼時候候都只使用一種肯定的 I/O 類型, 使代碼在全部 I/O 處都保持一致. 所以, 咱們不但願用戶來決定是使用流仍是 printf + read/write. 相反, 咱們應該決定到底用哪種方式. 把日誌做爲特例是由於日誌是一個很是獨特的應用, 還有一些是歷史緣由.
流的支持者們主張流是不二之選, 但觀點並非那麼清晰有力. 他們指出的流的每一個優點也都是其劣勢. 流最大的優點是在輸出時不須要關心打印對象的類型. 這是一個亮點. 同時, 也是一個不足: 你很容易用錯類型, 而編譯器不會報警. 使用流時容易形成的這類錯誤:
cout << this; // Prints the address cout << *this; // Prints the contents
因爲 << 被重載, 編譯器不會報錯. 就由於這一點咱們反對使用操做符重載.
有人說 printf 的格式化醜陋不堪, 易讀性差, 但流也好不到哪兒去. 看看下面兩段代碼吧, 實現相同的功能, 哪一個更清晰?
cerr << "Error connecting to '" << foo->bar()->hostname.first << ":" << foo->bar()->hostname.second << ": " << strerror(errno); fprintf(stderr, "Error connecting to '%s:%u: %s", foo->bar()->hostname.first, foo->bar()->hostname.second, strerror(errno));
你可能會說, 「把流封裝一下就會比較好了」, 這兒能夠, 其餘地方呢? 並且不要忘了, 咱們的目標是使語言更緊湊, 而不是添加一些別人須要學習的新裝備.
每一種方式都是各有利弊, 「沒有最好, 只有更適合」. 簡單性原則告誡咱們必須從中選擇其一, 最後大多數決定採用 printf + read/write.
Tip
對於迭代器和其餘模板對象使用前綴形式 (++i) 的自增, 自減運算符.
定義:
對於變量在自增 (++i 或 i++) 或自減 (--i 或 i--) 後表達式的值又沒有沒用到的狀況下, 須要肯定究竟是使用前置仍是後置的自增 (自減).
優勢:
不考慮返回值的話, 前置自增 (++i) 一般要比後置自增 (i++) 效率更高. 由於後置自增 (或自減) 須要對錶達式的值 i 進行一次拷貝. 若是 i 是迭代器或其餘非數值類型, 拷貝的代價是比較大的. 既然兩種自增方式實現的功能同樣, 爲何不老是使用前置自增呢?
缺點:
在 C 開發中, 當表達式的值未被使用時, 傳統的作法是使用後置自增, 特別是在 for 循環中. 有些人以爲後置自增更加易懂, 由於這很像天然語言, 主語 (i) 在謂語動詞 (++) 前.
結論:
對簡單數值 (非對象), 兩種都無所謂. 對迭代器和模板類型, 使用前置自增 (自減).
Tip
咱們強烈建議你在任何可能的狀況下都要使用 const.
定義:
在聲明的變量或參數前加上關鍵字 const 用於指明變量值不可被篡改 (如 const int foo ). 爲類中的函數加上 const 限定符代表該函數不會修改類成員變量的狀態 (如 classFoo { int Bar(char c) const; };).
優勢:
你們更容易理解如何使用變量. 編譯器能夠更好地進行類型檢測, 相應地, 也能生成更好的代碼. 人們對編寫正確的代碼更加自信, 由於他們知道所調用的函數被限定了能或不能修改變量值. 即便是在無鎖的多線程編程中, 人們也知道什麼樣的函數是安全的.
缺點:
const 是入侵性的: 若是你向一個函數傳入 const 變量, 函數原型聲明中也必須對應 const 參數 (不然變量須要 const_cast 類型轉換), 在調用庫函數時顯得尤爲麻煩.
結論:
const 變量, 數據成員, 函數和參數爲編譯時類型檢測增長了一層保障; 便於儘早發現錯誤. 所以, 咱們強烈建議在任何可能的狀況下使用 const:
- 若是函數不會修改傳入的引用或指針類型參數, 該參數應聲明爲 const.
- 儘量將函數聲明爲 const. 訪問函數應該老是 const. 其餘不會修改任何數據成員, 未調用非 const 函數, 不會返回數據成員非 const 指針或引用的函數也應該聲明成 const.
- 若是數據成員在對象構造以後再也不發生變化, 可將其定義爲 const.
然而, 也不要發了瘋似的使用 const. 像 const int * const * const x; 就有些過了, 雖然它很是精確的描述了常量 x. 關注真正有幫助意義的信息: 前面的例子寫成 constint** x 就夠了.
關鍵字 mutable 能夠使用, 可是在多線程中是不安全的, 使用時首先要考慮線程安全.
const 的位置:
有人喜歡 int const *foo 形式, 不喜歡 const int* foo, 他們認爲前者更一致所以可讀性也更好: 遵循了 const 總位於其描述的對象以後的原則. 可是一致性原則不適用於此, 「不要過分使用」 的聲明能夠取消大部分你本來想保持的一致性. 將 const 放在前面才更易讀, 由於在天然語言中形容詞 (const) 是在名詞 (int) 以前.
這是說, 咱們提倡但不強制 const 在前. 但要保持代碼的一致性! (yospaly 注: 也就是不要在一些地方把 const 寫在類型前面, 在其餘地方又寫在後面, 肯定一種寫法, 而後保持一致.)
Tip
C++ 內建整型中, 僅使用 int. 若是程序中須要不一樣大小的變量, 能夠使用 <stdint.h> 中長度精確的整型, 如 int16_t.
定義:
C++ 沒有指定整型的大小. 一般人們假定 short 是 16 位, int``是 32 位, ``long 是 32 位, long long 是 64 位.
優勢:
保持聲明統一.
缺點:
C++ 中整型大小因編譯器和體系結構的不一樣而不一樣.
結論:
<stdint.h> 定義了 int16_t, uint32_t, int64_t 等整型, 在須要確保整型大小時能夠使用它們代替 short, unsigned long long 等. 在 C 整型中, 只使用 int. 在合適的狀況下, 推薦使用標準類型如 size_t 和 ptrdiff_t.
若是已知整數不會太大, 咱們經常會使用 int, 如循環計數. 在相似的狀況下使用原生類型 int. 你能夠認爲 int 至少爲 32 位, 但不要認爲它會多於 32 位. 若是須要 64 位整型, 用int64_t 或 uint64_t.
對於大整數, 使用 int64_t.
不要使用 uint32_t 等無符號整型, 除非你是在表示一個位組而不是一個數值, 或是你須要定義二進制補碼溢出. 尤爲是不要爲了指出數值永不會爲負, 而使用無符號類型. 相反, 你應該使用斷言來保護數據.
關於無符號整數:
有些人, 包括一些教科書做者, 推薦使用無符號類型表示非負數. 這種作法試圖達到自我文檔化. 可是, 在 C 語言中, 這一優勢被由其致使的 bug 所淹沒. 看看下面的例子:
for (unsigned int i = foo.Length()-1; i >= 0; --i) ...
上述循環永遠不會退出! 有時 gcc 會發現該 bug 並報警, 但大部分狀況下都不會. 相似的 bug 還會出如今比較有符合變量和無符號變量時. 主要是 C 的類型提高機制會導致無符號類型的行爲出乎你的意料.
所以, 使用斷言來指出變量爲非負數, 而不是使用無符號型!
Tip
代碼應該對 64 位和 32 位系統友好. 處理打印, 比較, 結構體對齊時應切記:
對於某些類型, printf() 的指示符在 32 位和 64 位系統上可移植性不是很好. C99 標準定義了一些可移植的格式化指示符. 不幸的是, MSVC 7.1 並不是所有支持, 並且標準中也有所遺漏, 因此有時咱們不得不本身定義一個醜陋的版本 (頭文件 inttypes.h 仿標準風格):
// printf macros for size_t, in the style of inttypes.h #ifdef _LP64 #define __PRIS_PREFIX "z" #else #define __PRIS_PREFIX #endif // Use these macros after a % in a printf format string // to get correct 32/64 bit behavior, like this: // size_t size = records.size(); // printf("%"PRIuS"\n", size); #define PRIdS __PRIS_PREFIX "d" #define PRIxS __PRIS_PREFIX "x" #define PRIuS __PRIS_PREFIX "u" #define PRIXS __PRIS_PREFIX "X" #define PRIoS __PRIS_PREFIX "o"
類型
不要使用
使用
備註
void *(或其餘指針類型)
%lx
%p
int64_t
%qd, %lld
%"PRId64"
uint64_t
%qu, %llu, %llx
%"PRIu64", %"PRIx64"
size_t
%u
%"PRIuS", %"PRIxS"
C99 規定 %zu
ptrdiff_t
%d
%"PRIdS"
C99 規定 %zd
注意 PRI* 宏會被編譯器擴展爲獨立字符串. 所以若是使用很是量的格式化字符串, 須要將宏的值而不是宏名插入格式中. 使用 PRI* 宏一樣能夠在 % 後包含長度指示符. 例如, printf("x = %30"PRIuS"\n", x) 在 32 位 Linux 上將被展開爲 printf("x = %30" "u" "\n", x), 編譯器當成 printf("x =%30u\n", x) 處理 (yospaly 注: 這在 MSVC 6.0 上行不通, VC 6 編譯器不會自動把引號間隔的多個字符串鏈接一個長字符串).
記住 sizeof(void *) != sizeof(int). 若是須要一個指針大小的整數要用 intptr_t.
你要很是當心的對待結構體對齊, 尤爲是要持久化到磁盤上的結構體 (yospaly 注: 持久化 - 將數據按字節流順序保存在磁盤文件或數據庫中). 在 64 位系統中, 任何含有int64_t/uint64_t 成員的類/結構體, 缺省都以 8 字節在結尾對齊. 若是 32 位和 64 位代碼要共用持久化的結構體, 須要確保兩種體系結構下的結構體對齊一致. 大多數編譯器都容許調整結構體對齊. gcc 中可以使用 __attribute__((packed)). MSVC 則提供了 #pragma pack() 和 __declspec(align()) (YuleFox 注, 解決方案的項目屬性裏也能夠直接設置).
建立 64 位常量時使用 LL 或 ULL 做爲後綴, 如:
int64_t my_value = 0×123456789LL; uint64_t my_mask = 3ULL << 48;
若是你確實須要 32 位和 64 位系統具備不一樣代碼, 能夠使用 #ifdef _LP64 指令來切分 32/64 位代碼. (儘可能不要這麼作, 若是非用不可, 儘可能使修改局部化)
Tip
使用宏時要很是謹慎, 儘可能之內聯函數, 枚舉和常量代替之.
宏意味着你和編譯器看到的代碼是不一樣的. 這可能會致使異常行爲, 尤爲由於宏具備全局做用域.
值得慶幸的是, C++ 中, 宏不像在 C 中那麼必不可少. 以往用宏展開性能關鍵的代碼, 如今能夠用內聯函數替代. 用宏表示常量可被 const 變量代替. 用宏 「縮寫」 長變量名可被引用代替. 用宏進行條件編譯... 這個, 千萬別這麼作, 會令測試更加痛苦 (#define 防止頭文件重包含固然是個特例).
宏能夠作一些其餘技術沒法實現的事情, 在一些代碼庫 (尤爲是底層庫中) 能夠看到宏的某些特性 (如用 # 字符串化, 用 ## 鏈接等等). 但在使用前, 仔細考慮一下能不能不使用宏達到一樣的目的.
下面給出的用法模式能夠避免使用宏帶來的問題; 若是你要宏, 儘量遵照:
- 不要在 .h 文件中定義宏.
- 在立刻要使用時才進行 #define, 使用後要當即 #undef.
- 不要只是對已經存在的宏使用#undef,選擇一個不會衝突的名稱;
- 不要試圖使用展開後會致使 C++ 構造不穩定的宏, 否則也至少要附上文檔說明其行爲.
Tip
整數用 0, 實數用 0.0, 指針用 NULL, 字符 (串) 用 '\0'.
整數用 0, 實數用 0.0, 這一點是毫無爭議的.
對於指針 (地址值), 究竟是用 0 仍是 NULL, Bjarne Stroustrup 建議使用最原始的 0. 咱們建議使用看上去像是指針的 NULL, 事實上一些 C++ 編譯器 (如 gcc 4.1.0) 對 NULL 進行了特殊的定義, 能夠給出有用的警告信息, 尤爲是 sizeof(NULL) 和 sizeof(0) 不相等的狀況.
字符 (串) 用 '\0', 不只類型正確並且可讀性好.
Tip
儘量用 sizeof(varname) 代替 sizeof(type).
使用 sizeof(varname) 是由於當代碼中變量類型改變時會自動更新. 某些狀況下 sizeof(type) 或許有意義, 但仍是要儘可能避免, 由於它會致使變量類型改變後不能同步.
Struct data; Struct data; memset(&data, 0, sizeof(data));
Warning
memset(&data, 0, sizeof(Struct));
Tip
只使用 Boost 中被承認的庫.
定義:
Boost 庫集 是一個廣受歡迎, 通過同行鑑定, 免費開源的 C++ 庫集.
優勢:
Boost代碼質量廣泛較高, 可移植性好, 填補了 C++ 標準庫不少空白, 如型別的特性, 更完善的綁定器, 更好的智能指針, 同時還提供了 TR1 (標準庫擴展) 的實現.
缺點:
某些 Boost 庫提倡的編程實踐可讀性差, 好比元編程和其餘高級模板技術, 以及過分 「函數化」 的編程風格.
結論:
爲了向閱讀和維護代碼的人員提供更好的可讀性, 咱們只容許使用 Boost 一部分經承認的特性子集. 目前容許使用如下庫:
- Compressed Pair : boost/compressed_pair.hpp
- Pointer Container : boost/ptr_container (序列化除外)
- Array : boost/array.hpp
- The Boost Graph Library (BGL) : boost/graph (序列化除外)
- Property Map : boost/property_map.hpp
- Iterator 中處理迭代器定義的部分 : boost/iterator/iterator_adaptor.hpp, boost/iterator/iterator_facade.hpp, 以及boost/function_output_iterator.hpp
咱們正在積極考慮增長其它 Boost 特性, 因此列表中的規則將不斷變化.
最重要的一致性規則是命名管理. 命名風格快速獲知名字表明是什麼東東: 類型? 變量? 函數? 常量? 宏 ... ? 甚至不須要去查找類型聲明. 咱們大腦中的模式匹配引擎能夠很是可靠的處理這些命名規則.
命名規則具備必定隨意性, 但相比按我的喜愛命名, 一致性更重, 因此無論你怎麼想, 規則總歸是規則.
Tip
函數命名, 變量命名, 文件命名應具有描述性; 不要過分縮寫. 類型和變量應該是名詞, 函數名能夠用 「命令性」 動詞.
如何命名:
儘量給出描述性的名稱. 不要節約行空間, 讓別人很快理解你的代碼更重要. 好的命名風格:
int num_errors; // Good. int num_completed_connections; // Good.
糟糕的命名使用含糊的縮寫或隨意的字符:
int n; // Bad - meaningless. int nerr; // Bad - ambiguous abbreviation. int n_comp_conns; // Bad - ambiguous abbreviation.
類型和變量名通常爲名詞: 如 FileOpener, num_errors.
函數名一般是指令性的 (確切的說它們應該是命令), 如 OpenFile(), set_num_errors(). 取值函數是個特例 (在 函數命名 處詳細闡述), 函數名和它要取值的變量同名.
縮寫:
除非該縮寫在其它地方都很是廣泛, 不然不要使用. 例如:
// Good // These show proper names with no abbreviations. int num_dns_connections; // 大部分人都知道 "DNS" 是啥意思. int price_count_reader; // OK, price count. 有意義.
Warning
// Bad! // Abbreviations can be confusing or ambiguous outside a small group. int wgc_connections; // Only your group knows what this stands for. int pc_reader; // Lots of things can be abbreviated "pc".
永遠不要用省略字母的縮寫:
int error_count; // Good. int error_cnt; // Bad.
Tip
文件名要所有小寫, 能夠包含下劃線 (_) 或連字符 (-). 按項目約定來.
可接受的文件命名:
my_useful_class.cc my-useful-class.cc myusefulclass.cc
C++ 文件要以 .cc 結尾, 頭文件以 .h 結尾.
不要使用已經存在於 /usr/include 下的文件名 (yospaly 注: 即編譯器搜索系統頭文件的路徑), 如 db.h.
一般應儘可能讓文件名更加明確. http_server_logs.h 就比 logs.h 要好. 定義類時文件名通常成對出現, 如 foo_bar.h 和 foo_bar.cc, 對應於類 FooBar.
內聯函數必須放在 .h 文件中. 若是內聯函數比較短, 就直接放在 .h 中. 若是代碼比較長, 能夠放到以 -inl.h 結尾的文件中. 對於包含大量內聯代碼的類, 能夠使用三個文件:
url_table.h // The class declaration. url_table.cc // The class definition. url_table-inl.h // Inline functions that include lots of code.
參考 -inl.h 文件 一節.
Tip
類型名稱的每一個單詞首字母均大寫, 不包含下劃線: MyExcitingClass, MyExcitingEnum.
全部類型命名 —— 類, 結構體, 類型定義 (typedef), 枚舉 —— 均使用相同約定. 例如:
// classes and structs class UrlTable { ... class UrlTableTester { ... struct UrlTableProperties { ... // typedefs typedef hash_map<UrlTableProperties *, string> PropertiesMap; // enums enum UrlTableErrors { ...
Tip
變量名一概小寫, 單詞之間用下劃線鏈接. 類的成員變量如下劃線結尾, 如:
my_exciting_local_variable my_exciting_member_variable_
普通變量命名:
舉例:
string table_name; // OK - uses underscore. string tablename; // OK - all lowercase.
Warning
string tableName; // Bad - mixed case.
結構體變量:
結構體的數據成員能夠和普通變量同樣, 不用像類那樣接下劃線:
struct UrlTableProperties { string name; int num_entries; }
結構體與類的討論參考 結構體 vs. 類 一節.
全局變量:
對全局變量沒有特別要求, 少用就好, 但若是你要用, 能夠用 g_ 或其它標誌做爲前綴, 以便更好的區分局部變量.
Tip
在名稱前加 k: kDaysInAWeek.
全部編譯時常量, 不管是局部的, 全局的仍是類中的, 和其餘變量稍微區別一下. k 後接大寫字母開頭的單詞::
const int kDaysInAWeek = 7;
Tip
常規函數使用大小寫混合, 取值和設值函數則要求與變量名匹配: MyExcitingFunction(), MyExcitingMethod(), my_exciting_member_variable(),set_my_exciting_member_variable().
常規函數:
函數名的每一個單詞首字母大寫, 沒有下劃線:
AddTableEntry() DeleteUrl()
取值和設值函數:
取值和設值函數要與存取的變量名匹配. 這兒摘錄一個類, num_entries_ 是該類的實例變量:
class MyClass { public: ... int num_entries() const { return num_entries_; } void set_num_entries(int num_entries) { num_entries_ = num_entries; } private: int num_entries_; };
其它很是短小的內聯函數名也能夠用小寫字母, 例如. 若是你在循環中調用這樣的函數甚至都不用緩存其返回值, 小寫命名就能夠接受.
Tip
名字空間用小寫字母命名, 並基於項目名稱和目錄結構: google_awesome_project.
關於名字空間的討論和如何命名, 參考 名字空間 一節.
Tip
枚舉的命名應當和 常量 或 宏 一致: kEnumName 或是 ENUM_NAME.
單獨的枚舉值應該優先採用 常量 的命名方式. 但 宏 方式的命名也能夠接受. 枚舉名 UrlTableErrors (以及 AlternateUrlTableErrors) 是類型, 因此要用大小寫混合的方式.
enum UrlTableErrors { kOK = 0, kErrorOutOfMemory, kErrorMalformedInput, }; enum AlternateUrlTableErrors { OK = 0, OUT_OF_MEMORY = 1, MALFORMED_INPUT = 2, };
2009 年 1 月以前, 咱們一直建議採用 宏 的方式命名枚舉值. 因爲枚舉值和宏之間的命名衝突, 直接致使了不少問題. 由此, 這裏改成優先選擇常量風格的命名方式. 新代碼應該儘量優先使用常量風格. 可是老代碼不必切換到常量風格, 除非宏風格確實會產生編譯期問題.
Tip
你並不打算 使用宏, 對吧? 若是你必定要用, 像這樣命名: MY_MACRO_THAT_SCARES_SMALL_CHILDREN.
參考 預處理宏 <preprocessor-macros>; 一般 不該該 使用宏. 若是不得不用, 其命名像枚舉命名同樣所有大寫, 使用下劃線:
#define ROUND(x) ... #define PI_ROUNDED 3.0
Tip
若是你命名的實體與已有 C/C++ 實體類似, 可參考現有命名策略.
bigopen():
函數名, 參照 open() 的形式
uint:
typedef
bigpos:
struct 或 class, 參照 pos 的形式
sparse_hash_map:
STL 類似實體; 參照 STL 命名約定
LONGLONG_MAX:
常量, 如同 INT_MAX
註釋雖然寫起來很痛苦, 但對保證代碼可讀性相當重要. 下面的規則描述瞭如何註釋以及在哪兒註釋. 固然也要記住: 註釋當然很重要, 但最好的代碼自己應該是自文檔化. 有意義的類型名和變量名, 要遠賽過要用註釋解釋的含糊不清的名字.
你寫的註釋是給代碼讀者看的: 下一個須要理解你的代碼的人. 慷慨些吧, 下一我的可能就是你!
Tip
使用 // 或 /* */, 統一就好.
// 或 /* */ 均可以; 但 // 更 經常使用. 要在如何註釋及註釋風格上確保統一.
Tip
在每個文件開頭加入版權公告, 而後是文件內容描述.
法律公告和做者信息:
每一個文件都應該包含如下項, 依次是:
- 版權聲明 (好比, Copyright 2008 Google Inc.)
- 許可證. 爲項目選擇合適的許可證版本 (好比, Apache 2.0, BSD, LGPL, GPL)
- 做者: 標識文件的原始做者.
若是你對原始做者的文件作了重大修改, 將你的信息添加到做者信息裏. 這樣當其餘人對該文件有疑問時能夠知道該聯繫誰.
文件內容:
緊接着版權許可和做者信息以後, 每一個文件都要用註釋描述文件內容.
一般, .h 文件要對所聲明的類的功能和用法做簡單說明. .cc 文件一般包含了更多的實現細節或算法技巧討論, 若是你感受這些實現細節或算法技巧討論對於理解 .h 文件有幫助, 能夠該註釋挪到 .h, 並在 .cc 中指出文檔在 .h.
不要簡單的在 .h 和 .cc 間複製註釋. 這種偏離了註釋的實際意義.
Tip
每一個類的定義都要附帶一份註釋, 描述類的功能和用法.
// Iterates over the contents of a GargantuanTable. Sample usage: // GargantuanTable_Iterator* iter = table->NewIterator(); // for (iter->Seek("foo"); !iter->done(); iter->Next()) { // process(iter->key(), iter->value()); // } // delete iter; class GargantuanTable_Iterator { ... };
若是你以爲已經在文件頂部詳細描述了該類, 想直接簡單的來上一句 「完整描述見文件頂部」 也不打緊, 但務必確保有這類註釋.
若是類有任何同步前提, 文檔說明之. 若是該類的實例可被多線程訪問, 要特別注意文檔說明多線程環境下相關的規則和常量使用.
Tip
函數聲明處註釋描述函數功能; 定義處描述函數實現.
函數聲明:
註釋位於聲明以前, 對函數功能及用法進行描述. 註釋使用敘述式 (「Opens the file」) 而非指令式 (「Open the file」); 註釋只是爲了描述函數, 而不是命令函數作什麼. 一般, 註釋不會描述函數如何工做. 那是函數定義部分的事情.
函數聲明處註釋的內容:
- 函數的輸入輸出.
- 對類成員函數而言: 函數調用期間對象是否須要保持引用參數, 是否會釋放這些參數.
- 若是函數分配了空間, 須要由調用者釋放.
- 參數是否能夠爲 NULL.
- 是否存在函數使用上的性能隱患.
- 若是函數是可重入的, 其同步前提是什麼?
舉例以下:
// Returns an iterator for this table. It is the client's // responsibility to delete the iterator when it is done with it, // and it must not use the iterator once the GargantuanTable object // on which the iterator was created has been deleted. // // The iterator is initially positioned at the beginning of the table. // // This method is equivalent to: // Iterator* iter = table->NewIterator(); // iter->Seek(""); // return iter; // If you are going to immediately seek to another place in the // returned iterator, it will be faster to use NewIterator() // and avoid the extra seek. Iterator* GetIterator() const;
但也要避免羅羅嗦嗦, 或作些顯而易見的說明. 下面的註釋就沒有必要加上 「returns false otherwise」, 由於已經暗含其中了:
// Returns true if the table cannot hold any more entries. bool IsTableFull();
註釋構造/析構函數時, 切記讀代碼的人知道構造/析構函數是幹啥的, 因此 「destroys this object」 這樣的註釋是沒有意義的. 註明構造函數對參數作了什麼 (例如, 是否取得指針全部權) 以及析構函數清理了什麼. 若是都是些可有可無的內容, 直接省掉註釋. 析構函數前沒有註釋是很正常的.
函數定義:
每一個函數定義時要用註釋說明函數功能和實現要點. 好比說說你用的編程技巧, 實現的大體步驟, 或解釋如此實現的理由, 爲何前半部分要加鎖然後半部分不須要.
不要 從 .h 文件或其餘地方的函數聲明處直接複製註釋. 簡要重述函數功能是能夠的, 但註釋重點要放在如何實現上.
Tip
一般變量名自己足以很好說明變量用途. 某些狀況下, 也須要額外的註釋說明.
類數據成員:
每一個類數據成員 (也叫實例變量或成員變量) 都應該用註釋說明用途. 若是變量能夠接受 NULL 或 -1 等警惕值, 須加以說明. 好比:
private: // Keeps track of the total number of entries in the table. // Used to ensure we do not go over the limit. -1 means // that we don't yet know how many entries the table has. int num_total_entries_;
全局變量:
和數據成員同樣, 全部全局變量也要註釋說明含義及用途. 好比:
// The total number of tests cases that we run through in this regression test. const int kNumTestCases = 6;
Tip
對於代碼中巧妙的, 晦澀的, 有趣的, 重要的地方加以註釋.
代碼前註釋:
巧妙或複雜的代碼段前要加註釋. 好比:
// Divide result by two, taking into account that x // contains the carry from the add. for (int i = 0; i < result->size(); i++) { x = (x << 8) + (*result)[i]; (*result)[i] = x >> 1; x &= 1; }
行註釋:
比較隱晦的地方要在行尾加入註釋. 在行尾空兩格進行註釋. 好比:
// If we have enough memory, mmap the data portion too. mmap_budget = max<int64>(0, mmap_budget - index_->length()); if (mmap_budget >= data_size_ && !MmapData(mmap_chunk_bytes, mlock)) return; // Error already logged.
注意, 這裏用了兩段註釋分別描述這段代碼的做用, 和提示函數返回時錯誤已經被記入日誌.
若是你須要連續進行多行註釋, 能夠使之對齊得到更好的可讀性:
DoSomething(); // Comment here so the comments line up. DoSomethingElseThatIsLonger(); // Comment here so there are two spaces between // the code and the comment. { // One space before comment when opening a new scope is allowed, // thus the comment lines up with the following comments and code. DoSomethingElse(); // Two spaces before line comments normally. }
NULL, true/false, 1, 2, 3...:
向函數傳入 NULL, 布爾值或整數時, 要註釋說明含義, 或使用常量讓代碼望文知意. 例如, 對比:
Warning
bool success = CalculateSomething(interesting_value, 10, false, NULL); // What are these arguments??
和:
bool success = CalculateSomething(interesting_value, 10, // Default base value. false, // Not the first time we're calling this. NULL); // No callback.
或使用常量或描述性變量:
const int kDefaultBaseValue = 10; const bool kFirstTimeCalling = false; Callback *null_callback = NULL; bool success = CalculateSomething(interesting_value, kDefaultBaseValue, kFirstTimeCalling, null_callback);
不容許:
注意 永遠不要 用天然語言翻譯代碼做爲註釋. 要假設讀代碼的人 C++ 水平比你高, 即使他/她可能不知道你的用意:
Warning
// 如今, 檢查 b 數組並確保 i 是否存在, // 下一個元素是 i+1. ... // 天哪. 使人崩潰的註釋.
Tip
注意標點, 拼寫和語法; 寫的好的註釋比差的要易讀的多.
註釋的一般寫法是包含正確大小寫和結尾句號的完整語句. 短一點的註釋 (如代碼行尾註釋) 能夠隨意點, 依然要注意風格的一致性. 完整的語句可讀性更好, 也能夠說明該註釋是完整的, 而不是一些不成熟的想法.
雖然被別人指出該用分號時卻用了逗號多少有些尷尬, 但清晰易讀的代碼仍是很重要的. 正確的標點, 拼寫和語法對此會有所幫助.
Tip
對那些臨時的, 短時間的解決方案, 或已經夠好但仍不完美的代碼使用 TODO 註釋.
TODO 註釋要使用全大寫的字符串 TODO, 在隨後的圓括號裏寫上你的大名, 郵件地址, 或其它身份標識. 冒號是可選的. 主要目的是讓添加註釋的人 (也是能夠請求提供更多細節的人) 可根據規範的 TODO 格式進行查找. 添加 TODO 註釋並不意味着你要本身來修正.
// TODO(kl@gmail.com): Use a "*" here for concatenation operator. // TODO(Zeke) change this to use relations.
若是加 TODO 是爲了在 「未來某一天作某事」, 能夠附上一個很是明確的時間 「Fix by November 2005」), 或者一個明確的事項 (「Remove this code when all clients can handle XML responses.」).
代碼風格和格式確實比較隨意, 但一個項目中全部人遵循同一風格是很是容易的. 個體未必贊成下述每一處格式規則, 但整個項目服從統一的編程風格是很重要的, 只有這樣才能讓全部人能很輕鬆的閱讀和理解代碼.
另外, 咱們寫了一個 emacs 配置文件 來幫助你正確的格式化代碼.
Tip
每一行代碼字符數不超過 80.
咱們也認識到這條規則是有爭議的, 但不少已有代碼都已經遵守這一規則, 咱們感受一致性更重要.
優勢:
提倡該原則的人主張強迫他們調整編輯器窗口大小很野蠻. 不少人同時並排開幾個代碼窗口, 根本沒有多餘空間拉伸窗口. 你們都把窗口最大尺寸加以限定, 而且 80 列寬是傳統標準. 爲何要改變呢?
缺點:
反對該原則的人則認爲更寬的代碼行更易閱讀. 80 列的限制是上個世紀 60 年代的大型機的古板缺陷; 現代設備具備更寬的顯示屏, 很輕鬆的能夠顯示更多代碼.
結論:
80 個字符是最大值.
特例:
Tip
儘可能不使用非 ASCII 字符, 使用時必須使用 UTF-8 編碼.
即便是英文, 也不該將用戶界面的文本硬編碼到源代碼中, 所以非 ASCII 字符要少用. 特殊狀況下能夠適當包含此類字符. 如, 代碼分析外部數據文件時, 能夠適當硬編碼數據文件中做爲分隔符的非 ASCII 字符串; 更常見的是 (不須要本地化的) 單元測試代碼可能包含非 ASCII 字符串. 此類狀況下, 應使用 UTF-8 編碼, 由於不少工具均可以理解和處理 UTF-8 編碼. 十六進制編碼也能夠, 能加強可讀性的狀況下尤爲鼓勵 —— 好比 "\xEF\xBB\xBF" 在 Unicode 中是 零寬度 無間斷 的間隔符號, 若是不用十六進制直接放在 UTF-8 格式的源文件中, 是看不到的. (yospaly 注: "\xEF\xBB\xBF" 一般用做 UTF-8 with BOM 編碼標記)
Tip
只使用空格, 每次縮進 2 個空格.
咱們使用空格縮進. 不要在代碼中使用制符表. 你應該設置編輯器將制符錶轉爲空格.
Tip
返回類型和函數名在同一行, 參數也儘可能放在同一行.
函數看上去像這樣:
ReturnType ClassName::FunctionName(Type par_name1, Type par_name2) { DoSomething(); ... }
若是同一行文本太多, 放不下全部參數:
ReturnType ClassName::ReallyLongFunctionName(Type par_name1, Type par_name2, Type par_name3) { DoSomething(); ... }
甚至連第一個參數都放不下:
ReturnType LongClassName::ReallyReallyReallyLongFunctionName( Type par_name1, // 4 space indent Type par_name2, Type par_name3) { DoSomething(); // 2 space indent ... }
注意如下幾點:
- 返回值老是和函數名在同一行;
- 左圓括號老是和函數名在同一行;
- 函數名和左圓括號間沒有空格;
- 圓括號與參數間沒有空格;
- 左大括號總在最後一個參數同一行的末尾處;
- 右大括號老是單獨位於函數最後一行;
- 右圓括號和左大括號間老是有一個空格;
- 函數聲明和實現處的全部形參名稱必須保持一致;
- 全部形參應儘量對齊;
- 缺省縮進爲 2 個空格;
- 換行後的參數保持 4 個空格的縮進;
若是函數聲明成 const, 關鍵字 const 應與最後一個參數位於同一行:=
// Everything in this function signature fits on a single line ReturnType FunctionName(Type par) const { ... } // This function signature requires multiple lines, but // the const keyword is on the line with the last parameter. ReturnType ReallyLongFunctionName(Type par1, Type par2) const { ... }
若是有些參數沒有用到, 在函數定義處將參數名註釋起來:
// Always have named parameters in interfaces. class Shape { public: virtual void Rotate(double radians) = 0; } // Always have named parameters in the declaration. class Circle : public Shape { public: virtual void Rotate(double radians); } // Comment out unused named parameters in definitions. void Circle::Rotate(double /*radians*/) {}
Warning
// Bad - if someone wants to implement later, it's not clear what the // variable means. void Circle::Rotate(double) {}
Tip
儘可能放在同一行, 不然, 將實參封裝在圓括號中.
函數調用遵循以下形式:
bool retval = DoSomething(argument1, argument2, argument3);
若是同一行放不下, 可斷爲多行, 後面每一行都和第一個實參對齊, 左圓括號後和右圓括號前不要留空格:
bool retval = DoSomething(averyveryveryverylongargument1, argument2, argument3);
若是函數參數不少, 出於可讀性的考慮能夠在每行只放一個參數:
bool retval = DoSomething(argument1, argument2, argument3, argument4);
若是函數名很是長, 以致於超過 行最大長度, 能夠將全部參數獨立成行:
if (...) { ... ... if (...) { DoSomethingThatRequiresALongFunctionName( very_long_argument1, // 4 space indent argument2, argument3, argument4); }
Tip
傾向於不在圓括號內使用空格. 關鍵字 else 另起一行.
對基本條件語句有兩種能夠接受的格式. 一種在圓括號和條件之間有空格, 另外一種沒有.
最多見的是沒有空格的格式. 哪一種均可以, 但 保持一致性. 若是你是在修改一個文件, 參考當前已有格式. 若是是寫新的代碼, 參考目錄下或項目中其它文件. 還在徘徊的話, 就不要加空格了.
if (condition) { // no spaces inside parentheses ... // 2 space indent. } else { // The else goes on the same line as the closing brace. ... }
若是你更喜歡在圓括號內部加空格:
if ( condition ) { // spaces inside parentheses - rare ... // 2 space indent. } else { // The else goes on the same line as the closing brace. ... }
注意全部狀況下 if 和左圓括號間都有個空格. 右圓括號和左大括號之間也要有個空格:
Warning
if(condition) // Bad - space missing after IF. if (condition){ // Bad - space missing before {. if(condition){ // Doubly bad.
if (condition) { // Good - proper space after IF and before {.
若是能加強可讀性, 簡短的條件語句容許寫在同一行. 只有當語句簡單而且沒有使用 else 子句時使用:
if (x == kFoo) return new Foo(); if (x == kBar) return new Bar();
若是語句有 else 分支則不容許:
Warning
// Not allowed - IF statement on one line when there is an ELSE clause if (x) DoThis(); else DoThat();
一般, 單行語句不須要使用大括號, 若是你喜歡用也沒問題; 複雜的條件或循環語句用大括號可讀性會更好. 也有一些項目要求 if 必須老是使用大括號:
if (condition) DoSomething(); // 2 space indent. if (condition) { DoSomething(); // 2 space indent. }
但若是語句中某個 if-else 分支使用了大括號的話, 其它分支也必須使用:
Warning
// Not allowed - curly on IF but not ELSE if (condition) { foo; } else bar; // Not allowed - curly on ELSE but not IF if (condition) foo; else { bar; }
// Curly braces around both IF and ELSE required because // one of the clauses used braces. if (condition) { foo; } else { bar; }
Tip
switch 語句能夠使用大括號分段. 空循環體應使用 {} 或 continue.
switch 語句中的 case 塊能夠使用大括號也能夠不用, 取決於你的我的喜愛. 若是用的話, 要按照下文所述的方法.
若是有不知足 case 條件的枚舉值, switch 應該老是包含一個 default 匹配 (若是有輸入值沒有 case 去處理, 編譯器將報警). 若是 default 應該永遠執行不到, 簡單的加條 assert:
switch (var) { case 0: { // 2 space indent ... // 4 space indent break; } case 1: { ... break; } default: { assert(false); } }
空循環體應使用 {} 或 continue, 而不是一個簡單的分號.
while (condition) { // Repeat test until it returns false. } for (int i = 0; i < kSomeNumber; ++i) {} // Good - empty body. while (condition) continue; // Good - continue indicates no logic.
Warning
while (condition); // Bad - looks like part of do/while loop.
Tip
句點或箭頭先後不要有空格. 指針/地址操做符 (*, &) 以後不能有空格.
下面是指針和引用表達式的正確使用範例:
x = *p; p = &x; x = r.y; x = r->y;
注意:
在聲明指針變量或參數時, 星號與類型或變量名緊挨均可以:
// These are fine, space preceding. char *c; const string &str; // These are fine, space following. char* c; // but remember to do "char* c, *d, *e, ...;"! const string& str;
Warning
char * c; // Bad - spaces on both sides of * const string & str; // Bad - spaces on both sides of &
在單個文件內要保持風格一致, 因此, 若是是修改現有文件, 要遵守該文件的風格.
Tip
若是一個布爾表達式超過 標準行寬, 斷行方式要統一一下.
下例中, 邏輯與 (&&) 操做符總位於行尾:
if (this_one_thing > this_other_thing && a_third_thing == a_fourth_thing && yet_another & last_one) { ... }
注意, 上例的邏輯與 (&&) 操做符均位於行尾. 能夠考慮額外插入圓括號, 合理使用的話對加強可讀性是頗有幫助的.
Tip
return 表達式中不要用圓括號包圍.
函數返回時不要使用圓括號:
return x; // not return(x);
Tip
用 = 或 () 都可.
在兩者中作出選擇; 下面的方式都是正確的:
int x = 3; int x(3); string name("Some Name"); string name = "Some Name";
Tip
預處理指令不要縮進, 從行首開始.
即便預處理指令位於縮進代碼塊中, 指令也應從行首開始.
// Good - directives at beginning of line if (lopsided_score) { #if DISASTER_PENDING // Correct -- Starts at beginning of line DropEverything(); #endif BackToNormal(); }
Warning
// Bad - indented directives if (lopsided_score) { #if DISASTER_PENDING // Wrong! The "#if" should be at beginning of line DropEverything(); #endif // Wrong! Do not indent "#endif" BackToNormal(); }
Tip
訪問控制塊的聲明依次序是 public:, protected:, private:, 每次縮進 1 個空格.
類聲明 (對類註釋不瞭解的話, 參考 類註釋) 的基本格式以下:
class MyClass : public OtherClass { public: // Note the 1 space indent! MyClass(); // Regular 2 space indent. explicit MyClass(int var); ~MyClass() {} void SomeFunction(); void SomeFunctionThatDoesNothing() { } void set_some_var(int var) { some_var_ = var; } int some_var() const { return some_var_; } private: bool SomeInternalFunction(); int some_var_; int some_other_var_; DISALLOW_COPY_AND_ASSIGN(MyClass); };
注意事項:
Tip
構造函數初始化列表放在同一行或按四格縮進並排幾行.
下面兩種初始化列表方式均可以接受:
// When it all fits on one line: MyClass::MyClass(int var) : some_var_(var), some_other_var_(var + 1) {
或
// When it requires multiple lines, indent 4 spaces, putting the colon on // the first initializer line: MyClass::MyClass(int var) : some_var_(var), // 4 space indent some_other_var_(var + 1) { // lined up ... DoSomething(); ... }
Tip
名字空間內容不縮進.
名字空間 不要增長額外的縮進層次, 例如:
namespace { void foo() { // Correct. No extra indentation within namespace. ... } } // namespace
不要縮進名字空間:
Warning
namespace { // Wrong. Indented when it should not be. void foo() { ... } } // namespace
Tip
水平留白的使用因地制宜. 永遠不要在行尾添加沒意義的留白.
常規:
void f(bool b) { // Open braces should always have a space before them. ... int i = 0; // Semicolons usually have no space before them. int x[] = { 0 }; // Spaces inside braces for array initialization are int x[] = {0}; // optional. If you use them, put them on both sides! // Spaces around the colon in inheritance and initializer lists. class Foo : public Bar { public: // For inline function implementations, put spaces between the braces // and the implementation itself. Foo(int b) : Bar(), baz_(b) {} // No spaces inside empty braces. void Reset() { baz_ = 0; } // Spaces separating braces from implementation. ...
添加冗餘的留白會給其餘人編輯時形成額外負擔. 所以, 行尾不要留空格. 若是肯定一行代碼已經修改完畢, 將多餘的空格去掉; 或者在專門清理空格時去掉(確信沒有其餘人在處理). (yospaly 注: 如今大部分代碼編輯器稍加設置後, 都支持自動刪除行首/行尾空格, 若是不支持, 考慮換一款編輯器或 IDE)
循環和條件語句:
if (b) { // Space after the keyword in conditions and loops. } else { // Spaces around else. } while (test) {} // There is usually no space inside parentheses. switch (i) { for (int i = 0; i < 5; ++i) { switch ( i ) { // Loops and conditions may have spaces inside if ( test ) { // parentheses, but this is rare. Be consistent. for ( int i = 0; i < 5; ++i ) { for ( ; i < 5 ; ++i) { // For loops always have a space after the ... // semicolon, and may have a space before the // semicolon. switch (i) { case 1: // No space before colon in a switch case. ... case 2: break; // Use a space after a colon if there's code after it.
操做符:
x = 0; // Assignment operators always have spaces around // them. x = -5; // No spaces separating unary operators and their ++x; // arguments. if (x && !y) ... v = w * x + y / z; // Binary operators usually have spaces around them, v = w*x + y/z; // but it's okay to remove spaces around factors. v = w * (x + z); // Parentheses should have no spaces inside them.
模板和轉換:
vector<string> x; // No spaces inside the angle y = static_cast<char*>(x); // brackets (< and >), before // <, or between >( in a cast. vector<char *> x; // Spaces between type and pointer are // okay, but be consistent. set<list<string> > x; // C++ requires a space in > >. set< list<string> > x; // You may optionally make use // symmetric spacing in < <.
Tip
垂直留白越少越好.
這不只僅是規則而是原則問題了: 不在萬不得已, 不要使用空行. 尤爲是: 兩個函數定義之間的空行不要超過 2 行, 函數體首尾不要留空行, 函數體中也不要隨意添加空行.
基本原則是: 同一屏能夠顯示的代碼越多, 越容易理解程序的控制流. 固然, 過於密集的代碼塊和過於疏鬆的代碼塊一樣難看, 取決於你的判斷. 但一般是垂直留白越少越好.
Warning
函數首尾不要有空行
void Function() { // Unnecessary blank lines before and after }
Warning
代碼塊首尾不要有空行
while (condition) { // Unnecessary blank line after } if (condition) { // Unnecessary blank line before }
if-else 塊之間空一行是能夠接受的:
if (condition) { // Some lines of code too small to move to another function, // followed by a blank line. } else { // Another block of code }
前面說明的編程習慣基本都是強制性的. 但全部優秀的規則都容許例外, 這裏就是探討這些特例.
Tip
對於現有不符合既定編程風格的代碼能夠網開一面.
當你修改使用其餘風格的代碼時, 爲了與代碼原有風格保持一致能夠不使用本指南約定. 若是不放心能夠與代碼原做者或如今的負責人員商討, 記住, 一致性 包括原有的一致性.
Tip
Windows 程序員有本身的編程習慣, 主要源於 Windows 頭文件和其它 Microsoft 代碼. 咱們但願任何人均可以順利讀懂你的代碼, 因此針對全部平臺的 C++ 編程只給出一個單獨的指南.
若是你習慣使用 Windows 編碼風格, 這兒有必要重申一下某些你可能會忘記的指南:
- 不要使用匈牙利命名法 (好比把整型變量命名成 iNum). 使用 Google 命名約定, 包括對源文件使用 .cc 擴展名.
- Windows 定義了不少原生類型的同義詞 (YuleFox 注: 這一點, 我也很反感), 如 DWORD, HANDLE 等等. 在調用 Windows API 時這是徹底能夠接受甚至鼓勵的. 但仍是儘可能使用原有的 C++ 類型, 例如, 使用 const TCHAR * 而不是 LPCTSTR.
- 使用 Microsoft Visual C++ 進行編譯時, 將警告級別設置爲 3 或更高, 並將全部 warnings 看成 errors 處理.
- 不要使用 #pragma once; 而應該使用 Google 的頭文件保護規則. 頭文件保護的路徑應該相對於項目根目錄 (yospaly 注: 如 #ifndef SRC_DIR_BAR_H_, 參考 #define 保護 一節).
- 除非萬不得已, 不要使用任何非標準的擴展, 如 #pragma 和 __declspec. 容許使用 __declspec(dllimport) 和 __declspec(dllexport); 但你必須經過宏來使用, 好比 DLLIMPORT 和 DLLEXPORT, 這樣其餘人在分享使用這些代碼時很容易就去掉這些擴展.
在 Windows 上, 只有不多的一些狀況下, 咱們能夠偶爾違反規則:
- 一般咱們 禁止使用多重繼承, 但在使用 COM 和 ATL/WTL 類時能夠使用多重繼承. 爲了實現 COM 或 ATL/WTL 類/接口, 你可能不得不使用多重實現繼承.
- 雖然代碼中不該該使用異常, 可是在 ATL 和部分 STL(包括 Visual C++ 的 STL) 中異常被普遍使用. 使用 ATL 時, 應定義 _ATL_NO_EXCEPTIONS 以禁用異常. 你要研究一下是否可以禁用 STL 的異常, 若是沒法禁用, 啓用編譯器異常也能夠. (注意這只是爲了編譯 STL, 本身代碼裏仍然不要含異常處理.)
- 一般爲了利用頭文件預編譯, 每一個每一個源文件的開頭都會包含一個名爲 StdAfx.h 或 precompile.h 的文件. 爲了使代碼方便與其餘項目共享, 避免顯式包含此文件 (precompile.cc), 使用 /FI 編譯器選項以自動包含.
- 資源頭文件一般命名爲 resource.h, 且只包含宏的, 不須要遵照本風格指南.
Tip
運用常識和判斷力, 並 保持一致.
編輯代碼時, 花點時間看看項目中的其它代碼, 並熟悉其風格. 若是其它代碼中 if 語句使用空格, 那麼你也要使用. 若是其中的註釋用星號 (*) 圍成一個盒子狀, 你一樣要這麼作.
風格指南的重點在於提供一個通用的編程規範, 這樣你們能夠把精力集中在實現內容而不是表現形式上. 咱們展現了全局的風格規範, 但局部風格也很重要, 若是你在一個文件中新加的代碼和原有代碼風格相去甚遠, 這就破壞了文件自己的總體美觀, 也影響閱讀, 因此要儘可能避免.
好了, 關於編碼風格寫的夠多了; 代碼自己才更有趣. 盡情享受吧!
Revision 3.133 Benjy Weinberger Craig Silverstein Gregory Eitzmann Mark Mentovai Tashana Landray