學習筆記:google c++ 編程風格指南
目錄:
一、頭文件.................................................
二、作用域.................................................
三、C++類.................................................
四、智能指針和其他C++特性....................
五、命名約定.............................................
六、代碼註釋.............................................
七、格式....................................................
一、頭文件
1.#define保護
#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_
2.頭文件依賴
當一個頭文件被包含的同時也引入了一項新的依賴,只要該頭文件被修改,代碼就要重新編譯。
因此盡量少的包含頭文件,使用前置申明可以減少頭文件包含,如:
頭文件中用到類 File,但不需要訪問File的聲明,則頭文件中只需前置聲明 class File;無需#include "file/base/file.h"
使用指針成員*Foo替代對象成員Foo,這樣無需訪問類的定義。
能依賴聲明的就不要依賴定義。
3.內聯函數
只有當函數只有10行甚至更少時才會將其定義為內聯函數( inline function)。
當函數體比較小的時候,內聯該函數可以令目標代碼更加高效。對於存取函數以及其他一些比較短的關鍵執行函數。
4.-inl.h文件
復雜的內聯函數的定義,應放在後綴名為-inl.h 的頭文件中。
-inl.h文件還可用於函數模板的定義,從而使得模板定義可讀性增強。
-inl.h和其他頭文件一樣,也需要#define 保護。
5.函數參數順序
定義函數時,參數順序為:輸入參數在前,輸出參數在後。
輸入參數一般傳值或常數引用
6.包含文件的名稱及次序
次序如下:C庫、C++庫、其他庫的.h、項目內的.h。
C 系統文件
C++系統文件
其他庫頭文件
本項目內頭文件
二、作用域
1.命名空間
命名空間將全局作用域細分為不同的、具名的作用域,可有效防止全局作用域的命名沖突。
*.cc文件中可以使用不具名命名空間;
*.h文件中只能使用具名命名空間,不能使用不具名命名空間;
最好不要使用 using指示符,以保證命名空間下的所有名稱都可以正常使用。
2.嵌套類
可以在一個類中定義另一個類,嵌套類也稱成員類。
當嵌套(成員)類只在被嵌套類( enclosing class)中使用時很有用,將其置於被嵌套類作用域作
為被嵌套類的成員不會汙染其他作用域同名類。class Foo {
private:
// Bar 是嵌套在 Foo 中的成員類
class Bar {
...
};
};
3.
使用命名空間中的非成員函數或靜態成員函數,盡量不要使用全局函數。
4.局部變量
將函數變量盡可能置於最小作用域內,在聲明變量時將其初始化。
如果變量是一個對象,每次進入作用域都要調用其構造函數,每次退出作用域都要調用其析構函數,類似變量放到循環作用域外面聲明要高效的多。
5.全局變量
class類型的全局變量是被禁止的,內建類型的全局變量是允許的,當然多線程代碼中非常數全局變量也是
被禁止的。永遠不要使用函數返回值初始化全局變量。
三、C++類
1. 構造函數的職責
構造函數中只進行那些沒有實際意義的初始化,如果對象需要有意義的初始化,考慮使用另外的Init()方法並(或)增加一個成員標
記用於指示對象是否已經初始化成功。
2. 默認構造函數
如果一個類定義了若幹成員變量又沒有其他構造函數,需要定義一個默認構造函數(沒有參數),否則編譯器將自動生成默認構造函數。
3. 明確的構造函數
對單參數構造函數使用 C++關鍵字 explicit。
只有一個參數的構造函數可被用於轉換(隱式轉換生成一個新的對象),為避免構造函數被調用造成隱式轉換,可以將其聲明
為explicit。
4. 拷貝構造函數
僅在代碼中需要拷貝一個類對象的時候使用拷貝構造函數;不需要拷貝時應使用DISALLOW_COPY_AND_ASSIGN。
// 禁止使用拷貝構造函數和賦值操作的宏
// 應在類的 private:中使用
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&); \
void operator=(const TypeName&)
class Foo {
public:
Foo(int f);
~Foo();
private:
DISALLOW_COPY_AND_ASSIGN(Foo);
};
絕大多數情況下都應使用DISALLOW_COPY_AND_ASSIGN,如果類確實需要可拷貝,應在該類的頭文件中說明原由,並適當定義拷貝構造函數和賦值操作,註意在operator=中檢測自賦值(self-assignment)情況。
5.結構體和類
僅當只有數據時使用struct,其它一概使用class。
6.繼承
使用組合通常比使用繼承更適宜,如果使用繼承的話,只使用公共繼承。
實現繼承通過原封不動的重用基類代碼減少了代碼量,接口繼承可用於程序上增強類的特定API的功能。
限定僅在子類訪問的成員函數為protected,需要註意的是數據成員應始終為私有。
當重定義派生的虛函數時,在派生類中明確聲明其為virtual。根本原因:如果遺漏virtual,閱讀者需要檢索類的所有祖先以確定該函數是否為虛函數。
7.多重繼承
只有當最多一個基類中含有實現,其他基類都是以Interface為後綴的純接口類時才會使用多重繼承。
多重繼承允許子類擁有多個基類,要將作為純接口的基類和具有實現的基類區別開來。
8.接口
純接口:
1) 只有純虛函數("=0")和靜態函數(下文提到的析構函數除外);
2) 沒有非靜態數據成員;
3) 沒有定義任何構造函數。如果有,也不含參數,並且為protected;
4) 如果是子類,也只能繼承滿足上述條件並以Interface為後綴的類。
9. 操作符重載
一般不要重載操作符,尤其是賦值操作(operator=)比較陰險,應避免重載。如果需要的話,可以定義類似Equals()、CopyFrom()等函數。
10.存取控制
將數據成員私有化,並提供相關存取函數,如定義變量foo_及取值函數 foo()、賦值函數set_foo()。
存取函數的定義一般內聯在頭文件中。
11.聲明次序
定義次序如下:
public:
protected:
private:
如果那一塊沒有,直接忽略即可。
每一塊中,聲明次序一般如下:
1) typedefs 和 enums;
2) 常量;
3) 構造函數;
4) 析構函數;
5) 成員函數,含靜態成員函數;
6) 數據成員,含靜態數據成員。
宏DISALLOW_COPY_AND_ASSIGN置於private:塊之後,作為類的最後部分。
12.編寫短小函數
函數盡量短小、簡單,便於他人閱讀和修改代碼。
如果函數超過40行,可以考慮在不影響程序結構的情況下將其分割一下。
四、智能指針和其他C++特性
需要使用智能指針的話,scoped_ptr完全可以勝任。在非常特殊的情況下,應該只使用std::tr1::shared_ptr,任何情況下都不要使用auto_ptr。
1.引用參數
輸入參數為值或常數引用,輸出參數為指針;輸入參數可以是常數指針,但不能使用非常數引用形參。
void Foo(const string &in, string *out);
2.函數重載
僅在輸入參數類型不同、功能相同時使用重載函數(含構造函數),不要使用函數重載模仿缺省函數參數。
3.缺省參數
禁止使用缺省函數參數。
缺省參數為很少涉及的例外情況提供了少定義一些函數的方便。
缺省參數使得復制粘貼以前的代碼難以呈現所有參數,當缺省參數不適用於新代碼時可能導致重大問題。
4.變長數組和alloca
禁止使用變長數組和alloca()。
它們在堆棧(stack)上根據數據分配大小可能導致難以發現的內存泄漏。
5.友元
允許合理使用友元類及友元函數。
友元延伸了(但沒有打破)類的封裝界線,當你希望只允許另一個類訪問某個成員時,使用友元通常比將
其聲明為public 要好得多。
6.異常
不要使用 C++異常。
函數有可能在不確定的地方返回,從而導致代碼管理和調試困難。
7.運行時類型識別(Run-Time Type Information, RTTI)
除單元測試外,不要使用RTTI。
8.類型轉換(Casting)
使用static_cast<>()等C++的類型轉換,不要使用int y = (int)x或int y = int(x);。
使用C++風格而不要使用C風格類型轉換:
1) static_cast:和C風格轉換相似可做值的強制轉換,或指針的父類到子類的明確的向上轉換;
2) const_cast:移除const屬性;
3) reinterpret_cast:指針類型和整型或其他指針間不安全的相互轉換,僅在你對所做一切了然於心時使用;
4) dynamic_cast:除測試外不要使用,除單元測試外,如果你需要在運行時確定類型信息,說明設計有缺
陷(參考 RTTI)。
9.流(Streams)
只在記錄日誌時使用流,流是printf()和scanf()的替代。
10.前置自增減(++i/--i)
叠代器和其他模板對象使用前綴形式(++i)的自增、自減。
不考慮返回值的話,前置自增(++i)通常要比後置自增(i++)效率更高,因為後置的自增自減需要對表達式的值i進行一次拷貝,如果i是叠代器或其他非數值類型,拷貝的代價是比較大的。
11.const的使用
強烈建議你在任何可以使用的情況下都要使用const。
如果你向一個函數傳入const變量,函數原型中也必須是const的(否則變量需要const_cast類型轉換)
如果函數不會修改傳入的引用或指針類型的參數,這樣的參數應該為const;
const int* foo
12.整型
<stdint.h>定義了int16_t、uint32_t、int64_t等整型,在需要確定大小的整型時可以使用它們代替short、unsigned long long等。
不要使用uint32_t等無符號整型,除非你是在表示一個位組(bit pattern)而不是一個數值。
13.64位下的可移植性
代碼在64位和32位的系統中,原則上應該都比較友好。
14.預處理宏
使用宏時要謹慎,盡量以內聯函數、枚舉和常量代替之。
15.0和NULL
整數用0,實數用0.0,指針用NULL,字符(串)用‘\0‘。
16.sizeof
盡可能用sizeof(varname)代替sizeof(type)。
17.Boost庫
只使用Boost中被認可的庫。
五、命名約定
六、代碼註釋
1.註釋風格
使用//或/* */,統一就好。
2.文件註釋
在每一個文件開頭加入版權公告,然後是文件內容描述。
3.類註釋
每個類的定義要附著描述類的功能和用法的註釋。
4.函數註釋
函數聲明處註釋的內容:
1) inputs(輸入)及outputs(輸出);
2) 對類成員函數而言:函數調用期間對象是否需要保持引用參數,是否會釋放這些參數;
3) 如果函數分配了空間,需要由調用者釋放;
4) 參數是否可以為 NULL;
5) 是否存在函數使用的性能隱憂;
6) 如果函數是可重入的,其同步前提是什麽?
函數定義:
每個函數定義時要以註釋說明函數功能和實現要點,如使用的漂亮代碼、實現的簡要步驟、如此實現的理由、為什麽前半部分要加鎖而後半部分不需要。
5.變量註釋
通常變量名本身足以很好說明變量用途,特定情況下,需要額外註釋說明。
6.實現註釋
對於實現代碼中巧妙的、晦澀的、有趣的、重要的地方加以註釋。
7.標點、拼寫和語法
留意標點、拼寫和語法,寫的好的註釋比差的要易讀的多。
8.TODO註釋
對那些臨時的、短期的解決方案,或已經夠好但並不完美的代碼使用TODO註釋。
// TODO([email protected]): Use a "*" here for concatenation operator.
七、格式
1.行長度
每一行代碼字符數不超過80。
2.非ASCII字符
盡量不使用非ASCII字符,使用時必須使用UTF-8格式。
3.空格還是制表位
只使用空格,每次縮進2個空格。
4.函數聲明與定義
返回類型和函數名在同一行,合適的話,參數也放在同一行。
5.函數調用
盡量放在同一行,否則,將實參封裝在圓括號中。
6.條件語句
更提倡不在圓括號中添加空格,關鍵字else另起一行。
7.循環和開關選擇語句
switch語句可以使用大括號分塊;空循環體應使用{}或continue。
8.指針和引用表達式
句點(.)或箭頭(->)前後不要有空格,指針/地址操作符(*、&)後不要有空格。
x = *p;
p = &x;
x = r.y;
x = r->y;
在聲明指針變量或參數時,星號與類型或變量名緊挨都可以,個人比較習慣與變量緊挨的方式。
char *c;
const string &str;
或
char* c;
const string& str;
9.布爾表達式
如果一個布爾表達式超過標準行寬(80字符),如果斷行要統一一下。
10.函數返回值
return 表達式中不要使用圓括號。
11.變量及數組初始化
選擇=還是()。
12.預處理指令
預處理指令不要縮進,從行首開始。
即使預處理指令位於縮進代碼塊中,指令也應從行首開始。
13.類格式
關鍵詞 public:、 protected:、 private:要縮進1個空格,且這三個關鍵詞沒有縮進。
14.初始化列表
構造函數初始化列表放在同一行或按四格縮進並排幾行。
15.命名空間格式化
命名空間內容不縮進。
命名空間不添加額外縮進層次。
16.水平空白
不要在行尾添加無謂的空白。
17.垂直空白
垂直空白越少越好。
函數頭、尾不要有空行。
代碼塊頭、尾不要有空行。
學習筆記:google c++ 編程風格指南