頭文件
- 頭文件避免多重包含。
#ifndef PROJ_PATH_FILE_H_ - 能用前置聲明就不要使用
#include。 - 函數(shù)的參數(shù)順序:輸入?yún)?shù),輸出參數(shù)。
作用域
- 不要使用
using。 - 命名空間不需縮進(jìn)。
-
.c中可以使用using,.h中必須在函數(shù)、方法、類內(nèi)部使用。 - 非成員函數(shù)、靜態(tài)成員函數(shù)、全局函數(shù)盡量放到命名空間中。
- 局部變量聲明的時(shí)候賦值。在離第一次使用盡可能近的地方聲明。
- 可在
while循環(huán)中聲明變量限制其作用域。 - 注意有時(shí)需在循環(huán)外聲明變量,比如類的對(duì)象,在循環(huán)內(nèi)聲明需要循環(huán)調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)。
類
- 構(gòu)造函數(shù)只進(jìn)行一些非重要內(nèi)容的初始化,因?yàn)槿绻僮魇?huì)造成對(duì)象初始化失敗,進(jìn)入不確定狀態(tài)。
可能的話,使用Init()方法集中初始化有意義的數(shù)據(jù)。 - 如果類有很多個(gè)變量,最好設(shè)置默認(rèn)構(gòu)造函數(shù),否則編譯器可能會(huì)生成一個(gè)很糟糕的構(gòu)造函數(shù)。
- 對(duì)單個(gè)參數(shù)的構(gòu)造函數(shù)使用
explicit關(guān)鍵字,防止默認(rèn)強(qiáng)制類型轉(zhuǎn)換。 - 一般如果沒(méi)有拷貝構(gòu)造函數(shù),編譯器會(huì)自動(dòng)聲明拷貝構(gòu)造函數(shù),而且是
public的。
如果是單例模式需要禁止拷貝,則可以在private中聲明但不定義拷貝構(gòu)造函數(shù),這樣當(dāng)試圖使用它們時(shí)編譯器將報(bào)錯(cuò)。用宏來(lái)描述:
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \\
TypeName(const TypeName&); \\
void operator=(const TypeName&)
private:
DISALLOW_COPY_AND_ASSIGN(Foo);
- 當(dāng)只有數(shù)據(jù)時(shí)使用
struct,其他一概用class。struct只可以有構(gòu)造函數(shù),析構(gòu)函數(shù),Initialize(),Reset(),Validate()。 - 組合 > 實(shí)現(xiàn)繼承 > 接口繼承 > 私有繼承,子類重載的虛函數(shù)也要聲明
virtual關(guān)鍵字。 - 使用組合(
composition)通常比繼承更合理。如果需要類繼承,使用public繼承。 - 盡量做到
is-a繼承,在has-a繼承時(shí)盡量使用composition。 - 數(shù)據(jù)成員在任何情況下應(yīng)該都只是私有的。
- 當(dāng)定義一個(gè)虛函數(shù)時(shí),明確聲明其為
virtual,使得代碼閱讀者在檢查基類的時(shí)候易于判斷。
如果類有虛函數(shù),則析構(gòu)函數(shù)最好也聲明為virtual。 - 真正需要用到多重實(shí)現(xiàn)繼承的情況少之又少。
只在以下情況我們才允許多重繼承:最多只有一個(gè)基類是非抽象類,其它基類都是以Interface為后綴的純接口類。接口類類名以Interface為后綴。 - 為降低復(fù)雜性,盡量不重載操作符。
- 在類中使用特定的聲明順序:
public:在private:之前,成員函數(shù)在數(shù)據(jù)成員(變量)前。順序: typedefs和枚舉-
常量->構(gòu)造函數(shù) 析構(gòu)函數(shù)成員函數(shù), 含靜態(tài)成員函數(shù)數(shù)據(jù)成員, 含靜態(tài)數(shù)據(jù)成員- 將所有數(shù)據(jù)成員聲明為 private, 并根據(jù)需要提供相應(yīng)的存取函數(shù)。存取函數(shù)一般內(nèi)聯(lián)在頭文件中。
- 傾向編寫(xiě)簡(jiǎn)短, 凝練的函數(shù)。如果函數(shù)超過(guò) 40 行,可以思索一下能不能在不影響程序結(jié)構(gòu)的前提下對(duì)其進(jìn)行分割。
其他 C++ 特性
- 所有按引用傳遞參數(shù)必須加上
const。函數(shù)中非輸出參數(shù)最好都加上const。 - 在任何可能的地方加上
const。 - 如果你想重載一個(gè)函數(shù),考慮讓函數(shù)名包含參數(shù)信息,例如,使用
AppendString(),AppendInt()而不是Append()。 - 盡可能少的將函數(shù)的參數(shù)設(shè)置默認(rèn)值。
- 減少使用變長(zhǎng)數(shù)組。
- 允許合理的友元類和友元函數(shù)。
- 類型轉(zhuǎn)換時(shí)需要明確指明
static_cast,const_cast,reinterpret_cast,dynamic_cast。 - 只在打印日志的時(shí)候使用流。流最大的優(yōu)勢(shì)是在輸出時(shí)不需要關(guān)心打印對(duì)象的類型。
這是一個(gè)亮點(diǎn),同時(shí)也是一個(gè)不足:你很容易用錯(cuò)類型,而編譯器不會(huì)報(bào)警。
使用流時(shí)容易造成的這類錯(cuò)誤:
cout << this; // Prints the address
cout << *this; // Prints the contents
- 對(duì)于迭代器和其他模板對(duì)象使用前置的自增自減。
- 使用宏時(shí)要非常謹(jǐn)慎,盡量以內(nèi)聯(lián)函數(shù)、枚舉和常量代替之。
不要在.h文件中定義宏。
在馬上要使用時(shí)才進(jìn)行#define。使用后要立即#undef。 - 整數(shù)用 0,實(shí)數(shù)用0.0,指針用
NULL,字符(串)用'\\0'。 - 盡可能用
sizeof(varname)代替sizeof(type),代碼中變量類型改變后可以進(jìn)行自動(dòng)更新。
命名約定
- 盡可能給出描述性的名稱。不要節(jié)約行空間,讓別人很快理解你的代碼更重要。
int num_errors; // Good.
int num_completed_connections; // Good
- 類型和變量名一般為名詞:如
FileOpener,num_errors。
函數(shù)名通常是指令性的,如OpenFile(),set_num_errors()。
取值函數(shù)是個(gè)特例,函數(shù)名和它要取值的變量同名。 - 除非該縮寫(xiě)在其它地方都非常普遍,否則不要使用。永遠(yuǎn)不要用省略字母的縮寫(xiě)。
int error_count; // Good
int error_cnt; // Bad
- 文件名要全部小寫(xiě),可以包含下劃線(_)或連字符 (-)。可接受的文件命名:
my_useful_class.cc、my-useful-class.cc、yusefulclass.cc,通常應(yīng)盡量讓文件名更加明確。 - 類型名稱的每個(gè)單詞首字母均大寫(xiě),不包含下劃線,大駝峰法。
所有類型命名:類、結(jié)構(gòu)體、類型定義(typedef)、枚舉->均使用相同約定。 - 變量名一律小寫(xiě),單詞之間用下劃線連接。類的成員變量以下劃線結(jié)尾。
結(jié)構(gòu)體的數(shù)據(jù)成員可以和普通變量一樣,不用像類那樣接下劃線。 - 常量名稱前加
k,如:const int kDaysInAWeek = 7; - 常規(guī)函數(shù)的函數(shù)名的每個(gè)單詞首字母大寫(xiě),沒(méi)有下劃線。
- 取值和設(shè)值函數(shù)要與存取的變量名匹配。
int num_entries_;-
int num_entries() const { return num_entries_; }。 - 枚舉內(nèi)數(shù)據(jù)的命名應(yīng)當(dāng)和常量或宏一致:
kEnumName或是ENUM_NAME。 - 宏命名:全大小,下劃線。如
#define PI_ROUNDED 3.0。
注釋
- 注釋要言簡(jiǎn)意賅, 不要拖沓冗余, 復(fù)雜的東西簡(jiǎn)單化和簡(jiǎn)單的東西復(fù)雜化都是要被鄙視的。
- 注釋固然很重要, 但最好的代碼本身應(yīng)該是自文檔化。有意義的類型名和變量名,要遠(yuǎn)勝過(guò)要用注釋解釋的含糊不清的名字。
- 在每一個(gè)文件開(kāi)頭加入版權(quán)公告,然后是文件內(nèi)容描述。
每個(gè)文件都應(yīng)該包含以下項(xiàng), 依次是: - 版權(quán)聲明 (比如,
Copyright 2008 Google Inc.) - 許可證. 為項(xiàng)目選擇合適的許可證版本 (比如,
Apache 2.0,BSD,LGPL,GPL) - 作者: 標(biāo)識(shí)文件的原始作者.
- 通常,
.h文件要對(duì)所聲明的類的功能和用法作簡(jiǎn)單說(shuō)明,.cc文件通常包含了更多的實(shí)現(xiàn)細(xì)節(jié)或算法技巧討論。如果你感覺(jué)這些實(shí)現(xiàn)細(xì)節(jié)或算法技巧討論對(duì)于理解.h文件有幫助,可以該注釋挪到.h,并在.cc中指出文檔在.h。
不要簡(jiǎn)單的在.h和.cc間復(fù)制注釋,這種偏離了注釋的實(shí)際意義。 - 每個(gè)類的定義都要附帶一份注釋,描述類的功能和用法。
- 對(duì)于代碼中巧妙的,晦澀的,有趣的,重要的地方加以注釋。
- 比較隱晦的地方要在行尾空兩格進(jìn)行注釋。
- 向函數(shù)傳入
NULL, 布爾值或整數(shù)時(shí), 要注釋說(shuō)明含義, 或使用常量讓代碼望文知意:
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);
- 連續(xù)多行輸入需要對(duì)齊:
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.
}
- 注意永遠(yuǎn)不要用自然語(yǔ)言翻譯代碼作為注釋。
- TODO注釋
// TODO(kl@gmail.com): Use a "*" here for concatenation operator.
// TODO(Zeke) change this to use relations.
如果加 TODO 是為了在 “將來(lái)某一天做某事”, 可以附上一個(gè)非常明確的時(shí)間 “Fix by November 2005”), 或者一個(gè)明確的事項(xiàng) (“Remove this code when all clients can handle XML responses.”)。
格式
- 盡量不使用非 ASCII 字符, 使用時(shí)必須使用 UTF-8 編碼。
- 返回類型和函數(shù)名在同一行, 參數(shù)也盡量放在同一行。
函數(shù)看上去像這樣:
ReturnType ClassName::FunctionName(Type par_name1, Type par_name2) {
DoSomething();
...
}
如果同一行文本太多, 放不下所有參數(shù):
ReturnType ClassName::ReallyLongFunctionName(Type par_name1,
Type par_name2,
Type par_name3) {
DoSomething();
...
}
甚至連第一個(gè)參數(shù)都放不下:
ReturnType LongClassName::ReallyReallyReallyLongFunctionName(
Type par_name1, // 4 space indent
Type par_name2,
Type par_name3) {
DoSomething(); // 2 space indent
...
}
- 注意以下幾點(diǎn):
- 返回值總是和函數(shù)名在同一行;
- 左圓括號(hào)總是和函數(shù)名在同一行;
- 函數(shù)名和左圓括號(hào)間沒(méi)有空格;
- 圓括號(hào)與參數(shù)間沒(méi)有空格;
- 左大括號(hào)總在最后一個(gè)參數(shù)同一行的末尾處;
- 右大括號(hào)總是單獨(dú)位于函數(shù)最后一行;
- 右圓括號(hào)和左大括號(hào)間總是有一個(gè)空格;
- 函數(shù)聲明和實(shí)現(xiàn)處的所有形參名稱必須保持一致;
- 所有形參應(yīng)盡可能對(duì)齊;
- 缺省縮進(jìn)為 2 個(gè)空格;
- 換行后的參數(shù)保持 4 個(gè)空格的縮進(jìn);
- 如果函數(shù)聲明成
const, 關(guān)鍵字const應(yīng)與最后一個(gè)參數(shù)位于同一行:
// 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 {
...
}
- 如果有些參數(shù)沒(méi)有用到, 在函數(shù)定義處將參數(shù)名注釋起來(lái)。
// Comment out unused named parameters in definitions.
void Circle::Rotate(double /*radians*/) {}
- 函數(shù)調(diào)用遵循如下形式:
bool retval = DoSomething(argument1, argument2, argument3);
如果同一行放不下, 可斷為多行, 后面每一行都和第一個(gè)實(shí)參對(duì)齊, 左圓括號(hào)后和右圓括號(hào)前不要留空格:
bool retval = DoSomething(averyveryveryverylongargument1,
argument2, argument3);
如果函數(shù)參數(shù)很多, 出于可讀性的考慮可以在每行只放一個(gè)參數(shù):
bool retval = DoSomething(argument1,
argument2,
argument3,
argument4);
如果函數(shù)名非常長(zhǎng), 以至于超過(guò) 行最大長(zhǎng)度, 可以將所有參數(shù)獨(dú)立成行:
if (...) {
...
...
if (...) {
DoSomethingThatRequiresALongFunctionName(
very_long_argument1, // 4 space indent
argument2,
argument3,
argument4);
}
- 條件語(yǔ)句,如if和左圓括號(hào)間都有個(gè)空格。右圓括號(hào)和左大括號(hào)之間也要有個(gè)空格。
- switch語(yǔ)句:
switch (var) {
case 0: { // 2 space indent
... // 4 space indent
break;
}
case 1: {
...
break;
}
default: {
assert(false);
}
}
- 空循環(huán)體應(yīng)使用
{}或continue, 而不是一個(gè)簡(jiǎn)單的分號(hào)。
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.
- 指針和引用:句點(diǎn)或箭頭前后不要有空格. 指針/地址操作符 (*, &) 之后不能有空格。
- 邏輯與 (&&) 操作符總位于行尾:
if (this_one_thing > this_other_thing &&
a_third_thing == a_fourth_thing &&
yet_another & last_one) {
...
}
-
return表達(dá)式中不要用圓括號(hào)包圍。 - 預(yù)處理指令不要縮進(jìn),從行首開(kāi)始。
即使預(yù)處理指令位于縮進(jìn)代碼塊中, 指令也應(yīng)從行首開(kāi)始。
// Good - directives at beginning of line
if (lopsided_score) {
#if DISASTER_PENDING // Correct -- Starts at beginning of line
DropEverything();
#endif
BackToNormal();
}
- 訪問(wèn)控制塊的聲明依次序是
public:,protected:,private:, 每次縮進(jìn) 1 個(gè)空格。 - 除第一個(gè)關(guān)鍵詞(一般是
public)外, 其他關(guān)鍵詞前要空一行。這些關(guān)鍵詞后不要保留空行。 - 名字空間內(nèi)容不縮進(jìn)。
- 垂直留白越少越好。這不僅僅是規(guī)則而是原則問(wèn)題了: 不在萬(wàn)不得已, 不要使用空行,尤其是:
- 兩個(gè)函數(shù)定義之間的空行不要超過(guò) 2 行, 函數(shù)體首尾不要留空行, 函數(shù)體中也不要隨意添加空行。
- 基本原則是: 同一屏可以顯示的代碼越多,越容易理解程序的控制流。
- 當(dāng)然,過(guò)于密集的代碼塊和過(guò)于疏松的代碼塊同樣難看, 取決于你的判斷. 但通常是垂直留白越少越好。
結(jié)束語(yǔ)
- 運(yùn)用常識(shí)和判斷力,并保持一致。
- 風(fēng)格指南的重點(diǎn)在于提供一個(gè)通用的編程規(guī)范, 這樣大家可以把精力集中在實(shí)現(xiàn)內(nèi)容而不是表現(xiàn)形式上。我們展示了全局的風(fēng)格規(guī)范, 但局部風(fēng)格也很重要。
- 如果你在一個(gè)文件中新加的代碼和原有代碼風(fēng)格相去甚遠(yuǎn), 這就破壞了文件本身的整體美觀, 也影響閱讀, 所以要盡量避免。
參考閱讀
Google C++ 風(fēng)格指南 - 中文版
Google 開(kāi)源項(xiàng)目風(fēng)格指南