GOOGLE C++代碼規範總結:
開始一個項目之前,首先應該明確編碼規範。一個好的編碼習慣可以讓代碼更易於閱讀,同時也能讓程序員避免犯一些常見的錯誤。
爲了便於快速參考,此帖僅僅提取了關鍵點,具體的詳解請參考官方地址:
英文:Google C++ Style Guide
中文:C++ 風格指南
Content
一、頭文件
二、作用域
三、類
四、函數
五、來自GOOGLE的技巧
六、其他C++特性
七、命名約定
八、註釋
九、格式
十、例外
一、頭文件
- 通常每一個 .cc 文件都有一個對應的 .h 文件。
- 頭文件應該能夠自給自足(self-contained)。
- 所有頭文件都應該使用 #define 來防止頭文件被多重包含, 命名格式是
<PROJECT>_<PATH>_<FILE>_H_
。 - 儘可能地避免使用前置聲明。
- 只有當函數只有不大於 10 行時纔將其定義爲內聯函數。
- 頭文件包含順序:
1. 相關頭文件
2. C 系統文件
3. C++ 系統文件
4. 其他庫的 .h 文件
5. 本項目內 .h 文件
例子:
#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
#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"
#endif // FOO_BAR_BAZ_H_
二、作用域
- 鼓勵在 .cc 文件中使用匿名命名空間或 static 聲明。
- 禁止使用 using 指示(using-directive)和內聯命名空間(inline namespace)。
- 儘量不要用裸的全局函數,不要用類的靜態方法模擬出命名空間的效果。
- 將函數變量儘可能置於最小作用域內(考慮效率的循環體是個例外), 並在變量聲明時進行初始化。
- 禁止定義靜態儲存週期非POD變量。
例子:
#include "a.h"
DEFINE_FLAG(bool, someflag, false, "dummy flag");
namespace a {
inline void my_inline_function() { // 左對齊,不要縮進
// 限制在一個函數中的命名空間別名
namespace baz = ::foo::bar::baz;
...
}
} // namespace a
三、類
- 不要在構造函數中調用虛函數,也不要在無法報出錯誤時進行可能失敗的初始化。
- 對於轉換運算符和單參數構造函數, 使用
explicit
關鍵字。 - 如果你的類型需要,就讓它們支持拷貝 / 移動。 否則,就把隱式產生的拷貝和移動函數禁用。
- 僅當只有數據成員時使用
struct
, 其它一概使用class
。 - 優先考慮組合。如果用繼承的話,定義爲
public
繼承。 - 只在以下情況我們才允許多重繼承: 最多隻有一個基類是非抽象類;其它基類都是以 Interface 爲後綴的 純接口類。
- 除少數特定環境外,不要重載運算符。也不要創建用戶定義字面量。
- 所有數據成員聲明爲
private
, 除非是static const
類型成員。 - 類定義一般應以
public:
開始, 後跟protected:
,最後是private:
。每部分的內容按如下順序:
1. 類型 (包括 typedef, using 和嵌套的結構體與類)
2. 常量
3. 工廠函數, 構造函數, 賦值運算符, 析構函數, 其它函數(從左向右)
4. 數據成員
- 只有那些普通的, 或性能關鍵且短小的函數可以內聯在類定義中。
四、函數
- 通常函數的參數順序爲: 輸入參數在先, 後跟輸出參數。
- 傾向於編寫簡短, 凝練的函數(不超過40行)。
- 所有引用參數都必須是const。輸入參數是值參或
const
引用,輸出參數爲指針。輸入參數可以是const
指針, 但決不能是非const
的引用參數, 除非特殊要求(如swap)。 - 函數重載要讓人一目瞭然。比如用 AppendString() 和 AppendInt() 等代替重載多個 Append()。
- 只允許在非虛函數中使用缺省參數,且必須保證缺省參數的值始終一致。一般情況下建議使用函數重載。如果在每個調用點缺省參數的值都有可能不同, 缺省函數也不允許使用。
- 只有在常規寫法 (返回類型前置) 不便於書寫或不便於閱讀時使用返回類型後置語法。
五、來自GOOGLE的技巧
- 動態分配出的對象最好有單一且固定的所有主, 並通過智能指針傳遞所有權。
- 使用cpplint.py檢查風格錯誤。
六、其他C++特性
- 只在定義移動構造函數與移動賦值操作時使用右值引用. 不要使用 std::forward。
- 不允許使用變長數組和 alloca()。
- 通常友元應該定義在同一文件內, 避免代碼讀者跑到其它文件查找使用該私有成員的類。
- 不使用 C++ 異常。
- 禁止使用 RTTI。
- 不要使用 C 風格類型轉換,而應該使用 C++ 風格。
- 只在記錄日誌時使用流。
- 對於迭代器和其他模板對象使用前綴形式 (++i) 的自增,自減運算符。
- 在任何可能的情況下都要使用 const。對於int const *foo 和 const int* foo,提倡但不強制 const 在前,但要保持代碼的一致性。
- 在 C++11 裏,用 constexpr 來定義真正的常量,或實現常量初始化。
- C++ 內建整型中, 僅使用 int。如果需要,使用<stdint.h> 中長度精確的整型。
- 使用斷言來指出變量爲非負數, 而不是使用無符號型。
- 代碼應該對 64 位和 32 位系統友好。參考
- 創建 64 位常量時使用 LL 或 ULL 作爲後綴。
- 使用宏時要非常謹慎, 儘量以內聯函數, 枚舉和常量代替之。
- 整數用 0, 實數用 0.0, 指針用 nullptr 或 NULL, 字符 (串) 用 ‘\0’。
- 儘可能用 sizeof(varname) 代替 sizeof(type)。
- 用 auto 繞過煩瑣的類型名,只要可讀性好就繼續用,別用在局部變量之外的地方。
- 可以用列表初始化。
- 適當使用 lambda 表達式,所有捕獲都要顯式寫出來。
- 不要使用複雜的模板編程。
- 只使用 Boost 中被認可的庫。參考
- 適當用 C++11。
七、命名約定
- 命名要有描述性,少用縮寫(廣爲人知的縮寫是允許的)。
- 代碼文件名要全部小寫,可以包含下劃線 “_” 或連字符 “-”,依照項目的約定. 如果沒有約定, 那麼 “_” 更好。不要使用已經存在於
/usr/include
下的文件名。 - C++ 文件要以 .cc 結尾,頭文件以 .h 結尾。專門插入文本的文件則以 .inc 結尾。
- 類型名稱的每個單詞首字母均大寫,不包含下劃線:MyExcitingClass,MyExcitingEnum。
- 變量 (包括函數參數) 和數據成員名一律小寫,單詞之間用下劃線連接。類的成員變量以下劃線結尾,但結構體的就不用。
- 聲明爲 constexpr 或 const 的變量,或在程序運行期間其值始終保持不變的, 命名時以 “k” 開頭, 大小寫混合,如
kDaysInAWeek
。 - 常規函數使用大小寫混合,取值和設值函數則要求與變量名匹配:MyExcitingFunction(),MyExcitingMethod(),my_exciting_member_variable(),set_my_exciting_member_variable()。
- 命名空間以小寫字母命名. 最高級命名空間的名字取決於項目名稱。
- 枚舉的命名應當和 常量(優先) 或 宏 一致: kEnumName 或是 ENUM_NAME。
- 通常不應該使用宏。若要用,用大寫加下劃線。如MY_MACRO。
- 如果你命名的實體與已有 C/C++ 實體相似, 可參考現有命名策略。
八、註釋
- 使用 // 或 /* */,統一就好。
- 在每一個文件開頭加入 版權公告 和 文件內容 信息。
- 每個類的定義都要附帶一份註釋, 描述類的功能和用法。
// Iterates over the contents of a GargantuanTable.
// Example:
// GargantuanTableIterator* iter = table->NewIterator();
// for (iter->Seek("foo"); !iter->done(); iter->Next()) {
// process(iter->key(), iter->value());
// }
// delete iter;
class GargantuanTableIterator {
...
};
- 類的聲明和定義分開了(例如分別放在了 .h 和 .cc 文件中),此時,描述類用法的註釋應當和接口定義放在一起,描述類的操作和實現的註釋應當和實現放在一起。不要在 .h 和 .cc 之間複製註釋。
- 函數聲明處的註釋描述函數功能,定義處的註釋描述函數實現。註釋使用敘述式 (“Opens the file”) 而非指令式(“Open the file”)。要敘述的內容如下(也要避免囉嗦,顯而易見的就不必指明瞭):
函數的輸入輸出.
對類成員函數而言: 函數調用期間對象是否需要保持引用參數, 是否會釋放這些參數.
函數是否分配了必須由調用者釋放的空間.
參數是否可以爲空指針.
是否存在函數使用上的性能隱患.
如果函數是可重入的, 其同步前提是什麼?
// 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;
- 函數定義處的註釋重點要放在如何實現上。
- 如果變量的變量名和類型名不足以說明,就應該用註釋說明其用途。
- 在巧妙或者晦澀的代碼段前要加代碼前註釋:
// 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;
}
- 在巧妙或者晦澀的代碼行後要加行註釋,在行尾空兩格進行註釋或多行時注意對齊:
DoSomething(); // Comment here so the comments line up.
DoSomethingElseThatIsLonger(); // 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.
}
std::vector<string> list{
// Comments in braced lists describe the next element...
"First item",
// .. and should be aligned appropriately.
"Second item"};
DoSomething(); /* For trailing block comments, one space is fine. */
- 對於函數參數,用具名變量代替大段而複雜的嵌套表達式。
- 不要描述顯而易見的現象,不要用自然語言翻譯代碼作爲註釋。
- 通常是完整的帶結束標點的敘述語句,語法及標點風格要統一。
- 對那些臨時的, 短期的解決方案, 或已經夠好但仍不完美的代碼使用 TODO 註釋。
// TODO([email protected]): Use a "*" here for concatenation operator.
// TODO(Zeke) change this to use relations.
// TODO(bug 12345): remove the "Last visitors" feature
- 通過棄用註釋(DEPRECATED)以標記某接口點已棄用。
九、格式
- 每一行代碼字符數儘量不超過80。
- 儘量不使用非 ASCII 字符,使用時必須使用 UTF-8 編碼。
- 不應將用戶界面的文本硬編碼到源代碼中。
- 縮進只使用空格,每次2個空格。不要在代碼中使用製表符。
- 返回類型和函數名在同一行,參數也儘量放在同一行,如果放不下就對形參分行,分行方式與函數調用一致,其餘細節如下:
// 右圓括號和函數名、第一個參數名間沒有空格。
// 返回類型如果與函數名分行的話不縮進。
ReturnType LongClassName::ReallyReallyReallyLongFunctionName(
Type par_name1, // 4 space indent
Type par_name2,
Type par_name3) { // 右圓括號和左大括號間有一個空格且在同一行
DoSomething(); // 2 space indent
...
} // 單獨一行
// 未被使用的參數如果其用途不明顯的話, 在函數定義處將參數名註釋起來
void Circle::Rotate(double /*radians*/) {}
// 屬性,和展開爲屬性的宏,寫在函數聲明或定義的最前面
MUST_USE_RESULT bool IsOK();
- lambda表達式格式和函數一樣:
int x = 0;
auto add_to_x = [&x](int n) { x += n; };
std::set<int> blacklist = {7, 8, 9};
std::vector<int> digits = {3, 9, 1, 8, 4, 7, 1};
digits.erase(std::remove_if(digits.begin(), digits.end(), [&blacklist](int i) {
return blacklist.find(i) != blacklist.end();
}),
digits.end());
- 函數調用格式:
// 在同一行
bool retval = DoSomething(argument1, argument2, argument3);
// 對參數分行
bool retval = DoSomething(averyveryveryverylongargument1,
argument2, argument3);
if (...) {
...
...
if (...) {
// 縮進 4 空格對參數分行
DoSomething(
argument1, argument2, // 4 空格縮進
argument3, argument4);
}
// 複雜表達式參數,以具名對象方式傳入
int my_heuristic = scores[x] * y + bases[x];
bool retval = DoSomething(my_heuristic, x, y, z);
// 也可以直接用,加上註釋
bool retval = DoSomething(scores[x] * y + bases[x], // Score heuristic.
x, y, z);
// 如果參數有格式,也可以使用參數本身的格式
my_widget.Transform(x1, x2, x3,
y1, y2, y3,
z1, z2, z3);
- 列表初始化格式:
// 一行列表初始化示範.
return {foo, bar};
functioncall({foo, bar});
pair<int, int> p{foo, bar};
// 當不得不斷行時.
SomeFunction(
{"assume a zero-length name before {"}, // 假設在 { 前有長度爲零的名字.
some_other_function_parameter);
SomeType variable{
some, other, values,
{"assume a zero-length name before {"}, // 假設在 { 前有長度爲零的名字.
SomeOtherType{
"Very long string requiring the surrounding breaks.", // 非常長的字符串, 前後都需要斷行.
some, other values},
SomeOtherType{"Slightly shorter string", // 稍短的字符串.
some, other, values}};
SomeType variable{
"This is too long to fit all in one line"}; // 字符串過長, 因此無法放在同一行.
MyType m = { // 注意了, 您可以在 { 前斷行.
superlongvariablename1,
superlongvariablename2,
{short, interior, list},
{interiorwrappinglist,
interiorwrappinglist2}};
- 條件語句格式,重要的是堅持一種並始終保持下去:
if (condition) { // if後有空格,圓括號裏沒有空格.
... // 2 空格縮進.
} else if (...) { // else 與 if 的右括號同一行.
...
} else {
...
}
if (x == kFoo) return new Foo(); // 簡短的情況,可以不用大括號
if (condition)
DoSomething(); // 2 空格縮進.
// 只要其中一個分支用了大括號, 兩個分支都要用上大括號.
if (condition) {
foo;
} else {
bar;
}
- 循環語句和switch語句:
switch (var) {
case 0: { // 2 空格縮進
... // 4 空格縮進
break;
}
case 1: { //大括號可以用可以不用
...
break;
}
default: {
assert(false); // 如果永遠執行不到這裏,就加一條assert
}
}
// 循環體單行語句可以不用大括號
for (int i = 0; i < kSomeNumber; ++i)
printf("I love you\n");
// 也可以用大括號
for (int i = 0; i < kSomeNumber; ++i) {
printf("I take it back\n");
}
while (condition) {
// 反覆循環直到條件失效.
}
for (int i = 0; i < kSomeNumber; ++i) {} // 好 - 空循環體.
while (condition) continue; // 好 - contunue 表明沒有邏輯.
while (condition); // 差 - 看起來僅僅只是 while/loop 的部分之一.
- ".“或”->"前後不要有空格. 指針/取地址操作符 (*, &) 之後不能有空格。
x = *p;
p = &x;
x = r.y;
x = r->y;
// 好, 空格前置.
char *c;
const string &str;
// 好, 空格後置.
char* c;
const string& str;
int x, *y; // 不允許 - 在多重聲明中不能使用 & 或 *
char * c; // 差 - * 兩邊都有空格
const string & str; // 差 - & 兩邊都有空格.
- 布爾表達式格式:
if (this_one_thing > this_other_thing &&
a_third_thing == a_fourth_thing &&
yet_another && last_one) {
...
}
- return語句格式:
return result; // 返回值很簡單, 沒有圓括號.
// 可以用圓括號把複雜表達式圈起來, 改善可讀性.
return (some_long_condition &&
another_condition);
return (value); // 差 - 畢竟您從來不會寫 var = (value);
return(result); // 差 - return 可不是函數!
- 變量初始化:
// 以下都可以
int x = 3;
int x(3);
int x{3};
string name("Some Name");
string name = "Some Name";
string name{"Some Name"};
- 預處理指令從行首開始:
if (lopsided_score) {
#if DISASTER_PENDING // 正確 - 從行首開始
DropEverything();
# if NOTIFY // 非必要 - # 後跟空格
NotifyClient();
# endif
#endif
BackToNormal();
}
- 訪問控制塊的聲明依次序是 public:, protected:, private:, 每個都縮進 1 個空格:
class MyClass : public OtherClass {
public: // 注意有一個空格的縮進
MyClass(); // 標準的兩空格縮進
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_;
};
- 構造函數初始化列表:
// 如果所有變量能放在同一行:
MyClass::MyClass(int var) : some_var_(var) {
DoSomething();
}
// 如果不能放在同一行,
// 必須置於冒號後, 並縮進 4 個空格
MyClass::MyClass(int var)
: some_var_(var), some_other_var_(var + 1) {
DoSomething();
}
// 如果初始化列表需要置於多行, 將每一個成員放在單獨的一行
// 並逐行對齊
MyClass::MyClass(int var)
: some_var_(var), // 4 space indent
some_other_var_(var + 1) { // lined up
DoSomething();
}
// 右大括號 } 可以和左大括號 { 放在同一行
// 如果這樣做合適的話
MyClass::MyClass(int var)
: some_var_(var) {}
- 命名空間內容不縮進。聲明嵌套命名空間時,每個命名空間都獨立成行。
- 行尾不要加多餘的空格。
- 操作符:
// 賦值運算符前後總是有空格.
x = 0;
// 其它二元操作符也前後恆有空格, 不過對於表達式的子式可以不加空格.
// 圓括號內部沒有緊鄰空格.
v = w * x + y / z;
v = w*x + y/z;
v = w * (x + z);
// 在參數和一元操作符之間不加空格.
x = -5;
++x;
if (x && !y)
...
- 不在萬不得已,不要使用空行。兩函數之間的空行不要超過兩行。
十、例外
- 已有既定風格的項目。
- Windows代碼。