9. 格式
每個(gè)人都可能有自己的代碼風(fēng)格和格式, 但如果一個(gè)項(xiàng)目中的所有人都遵循同一風(fēng)格的話, 這個(gè)項(xiàng)目就能更順利地進(jìn)行. 每個(gè)人未必能同意下述的每一處格式規(guī)則, 而且其中的不少規(guī)則需要一定時(shí)間的適應(yīng), 但整個(gè)項(xiàng)目服從統(tǒng)一的編程風(fēng)格是很重要的, 只有這樣才能讓所有人輕松地閱讀和理解代碼.
為了幫助你正確的格式化代碼, 我們寫了一個(gè)emacs 配置文件.
9.1. 行長(zhǎng)度
總述
每一行代碼字符數(shù)不超過(guò) 80.
我們也認(rèn)識(shí)到這條規(guī)則是有爭(zhēng)議的, 但很多已有代碼都遵照這一規(guī)則, 因此我們感覺(jué)一致性更重要.
優(yōu)點(diǎn)
提倡該原則的人認(rèn)為強(qiáng)迫他們調(diào)整編輯器窗口大小是很野蠻的行為. 很多人同時(shí)并排開(kāi)幾個(gè)代碼窗口, 根本沒(méi)有多余的空間拉伸窗口. 大家都把窗口最大尺寸加以限定, 并且 80 列寬是傳統(tǒng)標(biāo)準(zhǔn). 那么為什么要改變呢?
缺點(diǎn)
反對(duì)該原則的人則認(rèn)為更寬的代碼行更易閱讀. 80 列的限制是上個(gè)世紀(jì) 60 年代的大型機(jī)的古板缺陷; 現(xiàn)代設(shè)備具有更寬的顯示屏, 可以很輕松地顯示更多代碼.
結(jié)論
80 個(gè)字符是最大值.
如果無(wú)法在不傷害易讀性的條件下進(jìn)行斷行, 那么注釋行可以超過(guò) 80 個(gè)字符, 這樣可以方便復(fù)制粘貼. 例如, 帶有命令示例或 URL 的行可以超過(guò) 80 個(gè)字符.
包含長(zhǎng)路徑的#include語(yǔ)句可以超出80列.
頭文件保護(hù)可以無(wú)視該原則.
9.2. 非 ASCII 字符
總述
盡量不使用非 ASCII 字符, 使用時(shí)必須使用 UTF-8 編碼.
說(shuō)明
即使是英文, 也不應(yīng)將用戶界面的文本硬編碼到源代碼中, 因此非 ASCII 字符應(yīng)當(dāng)很少被用到. 特殊情況下可以適當(dāng)包含此類字符. 例如, 代碼分析外部數(shù)據(jù)文件時(shí), 可以適當(dāng)硬編碼數(shù)據(jù)文件中作為分隔符的非 ASCII 字符串; 更常見(jiàn)的是 (不需要本地化的) 單元測(cè)試代碼可能包含非 ASCII 字符串. 此類情況下, 應(yīng)使用 UTF-8 編碼, 因?yàn)楹芏喙ぞ叨伎梢岳斫夂吞幚?UTF-8 編碼.
十六進(jìn)制編碼也可以, 能增強(qiáng)可讀性的情況下尤其鼓勵(lì) —— 比如"\xEF\xBB\xBF", 或者更簡(jiǎn)潔地寫作u8"\uFEFF", 在 Unicode 中是零寬度 無(wú)間斷的間隔符號(hào), 如果不用十六進(jìn)制直接放在 UTF-8 格式的源文件中, 是看不到的.
(Yang.Y 注:"\xEF\xBB\xBF"通常用作 UTF-8 with BOM 編碼標(biāo)記)
使用u8前綴把帶uXXXX轉(zhuǎn)義序列的字符串字面值編碼成 UTF-8. 不要用在本身就帶 UTF-8 字符的字符串字面值上, 因?yàn)槿绻幾g器不把源代碼識(shí)別成 UTF-8, 輸出就會(huì)出錯(cuò).
別用 C++11 的char16_t和char32_t, 它們和 UTF-8 文本沒(méi)有關(guān)系,wchar_t同理, 除非你寫的代碼要調(diào)用 Windows API, 后者廣泛使用了wchar_t.
9.3. 空格還是制表位
總述
只使用空格, 每次縮進(jìn) 2 個(gè)空格.
說(shuō)明
我們使用空格縮進(jìn). 不要在代碼中使用制表符. 你應(yīng)該設(shè)置編輯器將制表符轉(zhuǎn)為空格.
9.4. 函數(shù)聲明與定義
總述
返回類型和函數(shù)名在同一行, 參數(shù)也盡量放在同一行, 如果放不下就對(duì)形參分行, 分行方式與函數(shù)調(diào)用一致.
說(shuō)明
函數(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ù)名.
只有在參數(shù)未被使用或者其用途非常明顯時(shí), 才能省略參數(shù)名.
如果返回類型和函數(shù)名在一行放不下, 分行.
如果返回類型與函數(shù)聲明或定義分行了, 不要縮進(jìn).
左圓括號(hào)總是和函數(shù)名在同一行.
函數(shù)名和左圓括號(hào)間永遠(yuǎn)沒(méi)有空格.
圓括號(hào)與參數(shù)間沒(méi)有空格.
左大括號(hào)總在最后一個(gè)參數(shù)同一行的末尾處, 不另起新行.
右大括號(hào)總是單獨(dú)位于函數(shù)最后一行, 或者與左大括號(hào)同一行.
右圓括號(hào)和左大括號(hào)間總是有一個(gè)空格.
所有形參應(yīng)盡可能對(duì)齊.
缺省縮進(jìn)為 2 個(gè)空格.
換行后的參數(shù)保持 4 個(gè)空格的縮進(jìn).
未被使用的參數(shù), 或者根據(jù)上下文很容易看出其用途的參數(shù), 可以省略參數(shù)名:
class Foo { public: Foo(Foo&&); Foo(const Foo&); Foo& operator=(Foo&&); Foo& operator=(const Foo&);};
未被使用的參數(shù)如果其用途不明顯的話, 在函數(shù)定義處將參數(shù)名注釋起來(lái):
class Shape { public: virtual void Rotate(double radians) = 0;};class Circle : public Shape { public: void Rotate(double radians) override;};void Circle::Rotate(double /*radians*/) {}// 差 - 如果將來(lái)有人要實(shí)現(xiàn), 很難猜出變量的作用.void Circle::Rotate(double) {}
屬性, 和展開(kāi)為屬性的宏, 寫在函數(shù)聲明或定義的最前面, 即返回類型之前:
MUST_USE_RESULT bool IsOK();
9.5. Lambda 表達(dá)式
總述
Lambda 表達(dá)式對(duì)形參和函數(shù)體的格式化和其他函數(shù)一致; 捕獲列表同理, 表項(xiàng)用逗號(hào)隔開(kāi).
說(shuō)明
若用引用捕獲, 在變量名和&之間不留空格.
int x = 0;auto add_to_x = [&x](int n) { x += n; };
短 lambda 就寫得和內(nèi)聯(lián)函數(shù)一樣.
std::set
9.6. 函數(shù)調(diào)用
總述
要么一行寫完函數(shù)調(diào)用, 要么在圓括號(hào)里對(duì)參數(shù)分行, 要么參數(shù)另起一行且縮進(jìn)四格. 如果沒(méi)有其它顧慮的話, 盡可能精簡(jiǎn)行數(shù), 比如把多個(gè)參數(shù)適當(dāng)?shù)胤旁谕恍欣?
說(shuō)明
函數(shù)調(diào)用遵循如下形式:
bool retval = DoSomething(argument1, argument2, argument3);
如果同一行放不下, 可斷為多行, 后面每一行都和第一個(gè)實(shí)參對(duì)齊, 左圓括號(hào)后和右圓括號(hào)前不要留空格:
bool retval = DoSomething(averyveryveryverylongargument1, argument2, argument3);
參數(shù)也可以放在次行, 縮進(jìn)四格:
if (...) { ... ... if (...) { DoSomething( argument1, argument2, // 4 空格縮進(jìn) argument3, argument4); }
把多個(gè)參數(shù)放在同一行以減少函數(shù)調(diào)用所需的行數(shù), 除非影響到可讀性. 有人認(rèn)為把每個(gè)參數(shù)都獨(dú)立成行, 不僅更好讀, 而且方便編輯參數(shù). 不過(guò), 比起所謂的參數(shù)編輯, 我們更看重可讀性, 且后者比較好辦:
如果一些參數(shù)本身就是略復(fù)雜的表達(dá)式, 且降低了可讀性, 那么可以直接創(chuàng)建臨時(shí)變量描述該表達(dá)式, 并傳遞給函數(shù):
int my_heuristic = scores[x] * y + bases[x];bool retval = DoSomething(my_heuristic, x, y, z);
或者放著不管, 補(bǔ)充上注釋:
bool retval = DoSomething(scores[x] * y + bases[x], // Score heuristic. x, y, z);
如果某參數(shù)獨(dú)立成行, 對(duì)可讀性更有幫助的話, 那也可以如此做. 參數(shù)的格式處理應(yīng)當(dāng)以可讀性而非其他作為最重要的原則.
此外, 如果一系列參數(shù)本身就有一定的結(jié)構(gòu), 可以酌情地按其結(jié)構(gòu)來(lái)決定參數(shù)格式:
// 通過(guò) 3x3 矩陣轉(zhuǎn)換 widget.my_widget.Transform(x1, x2, x3, y1, y2, y3, z1, z2, z3);
9.7. 列表初始化格式
總述
您平時(shí)怎么格式化函數(shù)調(diào)用, 就怎么格式化列表初始化.
說(shuō)明
如果列表初始化伴隨著名字, 比如類型或變量名, 格式化時(shí)將將名字視作函數(shù)調(diào)用名,{}視作函數(shù)調(diào)用的括號(hào). 如果沒(méi)有名字, 就視作名字長(zhǎng)度為零.
// 一行列表初始化示范.return {foo, bar};functioncall({foo, bar});pair
9.9. 條件語(yǔ)句
總述
傾向于不在圓括號(hào)內(nèi)使用空格. 關(guān)鍵字if和else另起一行.
說(shuō)明
對(duì)基本條件語(yǔ)句有兩種可以接受的格式. 一種在圓括號(hào)和條件之間有空格, 另一種沒(méi)有.
最常見(jiàn)的是沒(méi)有空格的格式. 哪一種都可以, 最重要的是保持一致. 如果你是在修改一個(gè)文件, 參考當(dāng)前已有格式. 如果是寫新的代碼, 參考目錄下或項(xiàng)目中其它文件. 還在猶豫的話, 就不要加空格了.
if (condition) { // 圓括號(hào)里沒(méi)有空格. ... // 2 空格縮進(jìn).} else if (...) { // else 與 if 的右括號(hào)同一行. ...} else { ...}
如果你更喜歡在圓括號(hào)內(nèi)部加空格:
if ( condition ) { // 圓括號(hào)與空格緊鄰 - 不常見(jiàn) ... // 2 空格縮進(jìn).} else { // else 與 if 的右括號(hào)同一行. ...}
注意所有情況下if和左圓括號(hào)間都有個(gè)空格. 右圓括號(hào)和左大括號(hào)之間也要有個(gè)空格:
if(condition) // 差 - IF 后面沒(méi)空格.if (condition){ // 差 - { 前面沒(méi)空格.if(condition){ // 變本加厲地差.if (condition) { // 好 - IF 和 { 都與空格緊鄰.
如果能增強(qiáng)可讀性, 簡(jiǎn)短的條件語(yǔ)句允許寫在同一行. 只有當(dāng)語(yǔ)句簡(jiǎn)單并且沒(méi)有使用else子句時(shí)使用:
if (x == kFoo) return new Foo();if (x == kBar) return new Bar();
如果語(yǔ)句有else分支則不允許:
// 不允許 - 當(dāng)有 ELSE 分支時(shí) IF 塊卻寫在同一行if (x) DoThis();else DoThat();
通常, 單行語(yǔ)句不需要使用大括號(hào), 如果你喜歡用也沒(méi)問(wèn)題; 復(fù)雜的條件或循環(huán)語(yǔ)句用大括號(hào)可讀性會(huì)更好. 也有一些項(xiàng)目要求if必須總是使用大括號(hào):
if (condition) DoSomething(); // 2 空格縮進(jìn).if (condition) { DoSomething(); // 2 空格縮進(jìn).}
但如果語(yǔ)句中某個(gè)if-else分支使用了大括號(hào)的話, 其它分支也必須使用:
// 不可以這樣子 - IF 有大括號(hào) ELSE 卻沒(méi)有.if (condition) { foo;} else bar;// 不可以這樣子 - ELSE 有大括號(hào) IF 卻沒(méi)有.if (condition) foo;else { bar;}// 只要其中一個(gè)分支用了大括號(hào), 兩個(gè)分支都要用上大括號(hào).if (condition) { foo;} else { bar;}
9.9. 循環(huán)和開(kāi)關(guān)選擇語(yǔ)句
總述
switch語(yǔ)句可以使用大括號(hào)分段, 以表明 cases 之間不是連在一起的. 在單語(yǔ)句循環(huán)里, 括號(hào)可用可不用. 空循環(huán)體應(yīng)使用{}或continue.
說(shuō)明
switch語(yǔ)句中的case塊可以使用大括號(hào)也可以不用, 取決于你的個(gè)人喜好. 如果用的話, 要按照下文所述的方法.
如果有不滿足case條件的枚舉值,switch應(yīng)該總是包含一個(gè)default匹配 (如果有輸入值沒(méi)有 case 去處理, 編譯器將給出 warning). 如果default應(yīng)該永遠(yuǎn)執(zhí)行不到, 簡(jiǎn)單的加條assert:
switch (var) { case 0: { // 2 空格縮進(jìn) ... // 4 空格縮進(jìn) break; } case 1: { ... break; } default: { assert(false); }}
在單語(yǔ)句循環(huán)里, 括號(hào)可用可不用:
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");}
空循環(huán)體應(yīng)使用{}或continue, 而不是一個(gè)簡(jiǎn)單的分號(hào).
while (condition) { // 反復(fù)循環(huán)直到條件失效.}for (int i = 0; i < kSomeNumber; ++i) {} // 可 - 空循環(huán)體.while (condition) continue; // 可 - contunue 表明沒(méi)有邏輯.while (condition); // 差 - 看起來(lái)僅僅只是 while/loop 的部分之一.
9.10. 指針和引用表達(dá)式
總述
句點(diǎn)或箭頭前后不要有空格. 指針/地址操作符 (*,&) 之后不能有空格.
說(shuō)明
下面是指針和引用表達(dá)式的正確使用范例:
x = *p;p = &x;x = r.y;x = r->y;
注意:
在訪問(wèn)成員時(shí), 句點(diǎn)或箭頭前后沒(méi)有空格.
指針操作符*或&后沒(méi)有空格.
在聲明指針變量或參數(shù)時(shí), 星號(hào)與類型或變量名緊挨都可以:
// 好, 空格前置.char *c;const string &str;// 好, 空格后置.char* c;const string& str;int x, *y; // 不允許 - 在多重聲明中不能使用 & 或 *char * c; // 差 - * 兩邊都有空格const string & str; // 差 - & 兩邊都有空格.
在單個(gè)文件內(nèi)要保持風(fēng)格一致, 所以, 如果是修改現(xiàn)有文件, 要遵照該文件的風(fēng)格.
9.11. 布爾表達(dá)式
總述
如果一個(gè)布爾表達(dá)式超過(guò)標(biāo)準(zhǔn)行寬, 斷行方式要統(tǒng)一一下.
說(shuō)明
下例中, 邏輯與 (&&) 操作符總位于行尾:
if (this_one_thing > this_other_thing && a_third_thing == a_fourth_thing && yet_another && last_one) { ...}
注意, 上例的邏輯與 (&&) 操作符均位于行尾. 這個(gè)格式在 Google 里很常見(jiàn), 雖然把所有操作符放在開(kāi)頭也可以. 可以考慮額外插入圓括號(hào), 合理使用的話對(duì)增強(qiáng)可讀性是很有幫助的. 此外, 直接用符號(hào)形式的操作符, 比如&&和~, 不要用詞語(yǔ)形式的and和compl.
9.12. 函數(shù)返回值
總述
不要在return表達(dá)式里加上非必須的圓括號(hào).
說(shuō)明
只有在寫x=expr要加上括號(hào)的時(shí)候才在returnexpr;里使用括號(hào).
return result; // 返回值很簡(jiǎn)單, 沒(méi)有圓括號(hào).// 可以用圓括號(hào)把復(fù)雜表達(dá)式圈起來(lái), 改善可讀性.return (some_long_condition && another_condition);return (value); // 畢竟您從來(lái)不會(huì)寫 var = (value);return(result); // return 可不是函數(shù)!
9.13. 變量及數(shù)組初始化
總述
用=,()和{}均可.
說(shuō)明
您可以用=,()和{}, 以下的例子都是正確的:
int x = 3;int x(3);int x{3};string name("Some Name");string name = "Some Name";string name{"Some Name"};
請(qǐng)務(wù)必小心列表初始化{...}用std::initializer_list構(gòu)造函數(shù)初始化出的類型. 非空列表初始化就會(huì)優(yōu)先調(diào)用std::initializer_list, 不過(guò)空列表初始化除外, 后者原則上會(huì)調(diào)用默認(rèn)構(gòu)造函數(shù). 為了強(qiáng)制禁用std::initializer_list構(gòu)造函數(shù), 請(qǐng)改用括號(hào).
vector
此外, 列表初始化不允許整型類型的四舍五入, 這可以用來(lái)避免一些類型上的編程失誤.
int pi(3.14); // 好 - pi == 3.int pi{3.14}; // 編譯錯(cuò)誤: 縮窄轉(zhuǎn)換.
9.14. 預(yù)處理指令
總述
預(yù)處理指令不要縮進(jìn), 從行首開(kāi)始.
說(shuō)明
即使預(yù)處理指令位于縮進(jìn)代碼塊中, 指令也應(yīng)從行首開(kāi)始.
// 好 - 指令從行首開(kāi)始 if (lopsided_score) {#if DISASTER_PENDING // 正確 - 從行首開(kāi)始 DropEverything();# if NOTIFY // 非必要 - # 后跟空格 NotifyClient();# endif#endif BackToNormal(); }// 差 - 指令縮進(jìn) if (lopsided_score) { #if DISASTER_PENDING // 差 - "#if" 應(yīng)該放在行開(kāi)頭 DropEverything(); #endif // 差 - "#endif" 不要縮進(jìn) BackToNormal(); }
9.15. 類格式
總述
訪問(wèn)控制塊的聲明依次序是public:,protected:,private:, 每個(gè)都縮進(jìn) 1 個(gè)空格.
說(shuō)明
類聲明 (下面的代碼中缺少注釋, 參考類注釋) 的基本格式如下:
class MyClass : public OtherClass { public: // 注意有一個(gè)空格的縮進(jìn) MyClass(); // 標(biāo)準(zhǔn)的兩空格縮進(jìn) 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_;};
注意事項(xiàng):
所有基類名應(yīng)在 80 列限制下盡量與子類名放在同一行.
關(guān)鍵詞public:,protected:,private:要縮進(jìn) 1 個(gè)空格.
除第一個(gè)關(guān)鍵詞 (一般是public) 外, 其他關(guān)鍵詞前要空一行. 如果類比較小的話也可以不空.
這些關(guān)鍵詞后不要保留空行.
public放在最前面, 然后是protected, 最后是private.
關(guān)于聲明順序的規(guī)則請(qǐng)參考聲明順序一節(jié).
9.16. 構(gòu)造函數(shù)初始值列表
總述
構(gòu)造函數(shù)初始化列表放在同一行或按四格縮進(jìn)并排多行.
說(shuō)明
下面兩種初始值列表方式都可以接受:
// 如果所有變量能放在同一行:MyClass::MyClass(int var) : some_var_(var) { DoSomething();}// 如果不能放在同一行,// 必須置于冒號(hào)后, 并縮進(jìn) 4 個(gè)空格MyClass::MyClass(int var) : some_var_(var), some_other_var_(var + 1) { DoSomething();}// 如果初始化列表需要置于多行, 將每一個(gè)成員放在單獨(dú)的一行// 并逐行對(duì)齊MyClass::MyClass(int var) : some_var_(var), // 4 space indent some_other_var_(var + 1) { // lined up DoSomething();}// 右大括號(hào) } 可以和左大括號(hào) { 放在同一行// 如果這樣做合適的話MyClass::MyClass(int var) : some_var_(var) {}
9.17. 命名空間格式化
總述
命名空間內(nèi)容不縮進(jìn).
說(shuō)明
命名空間不要增加額外的縮進(jìn)層次, 例如:
namespace {void foo() { // 正確. 命名空間內(nèi)沒(méi)有額外的縮進(jìn). ...}} // namespace
不要在命名空間內(nèi)縮進(jìn):
namespace { // 錯(cuò), 縮進(jìn)多余了. void foo() { ... }} // namespace
聲明嵌套命名空間時(shí), 每個(gè)命名空間都獨(dú)立成行.
namespace foo {namespace bar {
9.19. 水平留白
總述
水平留白的使用根據(jù)在代碼中的位置決定. 永遠(yuǎn)不要在行尾添加沒(méi)意義的留白.
說(shuō)明
通用
void f(bool b) { // 左大括號(hào)前總是有空格. ...int i = 0; // 分號(hào)前不加空格.// 列表初始化中大括號(hào)內(nèi)的空格是可選的.// 如果加了空格, 那么兩邊都要加上.int x[] = { 0 };int x[] = {0};// 繼承與初始化列表中的冒號(hào)前后恒有空格.class Foo : public Bar { public: // 對(duì)于單行函數(shù)的實(shí)現(xiàn), 在大括號(hào)內(nèi)加上空格 // 然后是函數(shù)實(shí)現(xiàn) Foo(int b) : Bar(), baz_(b) {} // 大括號(hào)里面是空的話, 不加空格. void Reset() { baz_ = 0; } // 用括號(hào)把大括號(hào)與實(shí)現(xiàn)分開(kāi). ...
添加冗余的留白會(huì)給其他人編輯時(shí)造成額外負(fù)擔(dān). 因此, 行尾不要留空格. 如果確定一行代碼已經(jīng)修改完畢, 將多余的空格去掉; 或者在專門清理空格時(shí)去掉(尤其是在沒(méi)有其他人在處理這件事的時(shí)候). (Yang.Y 注: 現(xiàn)在大部分代碼編輯器稍加設(shè)置后, 都支持自動(dòng)刪除行首/行尾空格, 如果不支持, 考慮換一款編輯器或 IDE)
循環(huán)和條件語(yǔ)句
if (b) { // if 條件語(yǔ)句和循環(huán)語(yǔ)句關(guān)鍵字后均有空格.} else { // else 前后有空格.}while (test) {} // 圓括號(hào)內(nèi)部不緊鄰空格.switch (i) {for (int i = 0; i < 5; ++i) {switch ( i ) { // 循環(huán)和條件語(yǔ)句的圓括號(hào)里可以與空格緊鄰.if ( test ) { // 圓括號(hào), 但這很少見(jiàn). 總之要一致.for ( int i = 0; i < 5; ++i ) {for ( ; i < 5 ; ++i) { // 循環(huán)里內(nèi) ; 后恒有空格, ; ?前可以加個(gè)空格.switch (i) { case 1: // switch case 的冒號(hào)前無(wú)空格. ... case 2: break; // 如果冒號(hào)有代碼, 加個(gè)空格.
操作符
// 賦值運(yùn)算符前后總是有空格.x = 0;// 其它二元操作符也前后恒有空格, 不過(guò)對(duì)于表達(dá)式的子式可以不加空格.// 圓括號(hào)內(nèi)部沒(méi)有緊鄰空格.v = w * x + y / z;v = w*x + y/z;v = w * (x + z);// 在參數(shù)和一元操作符之間不加空格.x = -5;++x;if (x && !y) ...
模板和轉(zhuǎn)換
// 尖括號(hào)(< and >) 不與空格緊鄰, < 前沒(méi)有空格, > 和 ( 之間也沒(méi)有.vector
9.19. 垂直留白
總述
垂直留白越少越好.
說(shuō)明
這不僅僅是規(guī)則而是原則問(wèn)題了: 不在萬(wàn)不得已, 不要使用空行. 尤其是: 兩個(gè)函數(shù)定義之間的空行不要超過(guò) 2 行, 函數(shù)體首尾不要留空行, 函數(shù)體中也不要隨意添加空行.
基本原則是: 同一屏可以顯示的代碼越多, 越容易理解程序的控制流. 當(dāng)然, 過(guò)于密集的代碼塊和過(guò)于疏松的代碼塊同樣難看, 這取決于你的判斷. 但通常是垂直留白越少越好.
下面的規(guī)則可以讓加入的空行更有效:
函數(shù)體內(nèi)開(kāi)頭或結(jié)尾的空行可讀性微乎其微.
在多重 if-else 塊里加空行或許有點(diǎn)可讀性.
譯者 (YuleFox) 筆記
對(duì)于代碼格式, 因人, 系統(tǒng)而異各有優(yōu)缺點(diǎn), 但同一個(gè)項(xiàng)目中遵循同一標(biāo)準(zhǔn)還是有必要的;
行寬原則上不超過(guò) 80 列, 把 22 寸的顯示屏都占完, 怎么也說(shuō)不過(guò)去;
盡量不使用非 ASCII 字符, 如果使用的話, 參考 UTF-8 格式 (尤其是 UNIX/Linux 下, Windows 下可以考慮寬字符), 盡量不將字符串常量耦合到代碼中, 比如獨(dú)立出資源文件, 這不僅僅是風(fēng)格問(wèn)題了;
UNIX/Linux 下無(wú)條件使用空格, MSVC 的話使用 Tab 也無(wú)可厚非;
函數(shù)參數(shù), 邏輯條件, 初始化列表: 要么所有參數(shù)和函數(shù)名放在同一行, 要么所有參數(shù)并排分行;
除函數(shù)定義的左大括號(hào)可以置于行首外, 包括函數(shù)/類/結(jié)構(gòu)體/枚舉聲明, 各種語(yǔ)句的左大括號(hào)置于行尾, 所有右大括號(hào)獨(dú)立成行;
./->操作符前后不留空格,*/&不要前后都留, 一個(gè)就可, 靠左靠右依各人喜好;
預(yù)處理指令/命名空間不使用額外縮進(jìn), 類/結(jié)構(gòu)體/枚舉/函數(shù)/語(yǔ)句使用縮進(jìn);
初始化用=還是()依個(gè)人喜好, 統(tǒng)一就好;
return不要加();
水平/垂直留白不要濫用, 怎么易讀怎么來(lái).
關(guān)于 UNIX/Linux 風(fēng)格為什么要把左大括號(hào)置于行尾 (.cc文件的函數(shù)實(shí)現(xiàn)處, 左大括號(hào)位于行首), 我的理解是代碼看上去比較簡(jiǎn)約, 想想行首除了函數(shù)體被一對(duì)大括號(hào)封在一起之外, 只有右大括號(hào)的代碼看上去確實(shí)也舒服; Windows 風(fēng)格將左大括號(hào)置于行首的優(yōu)點(diǎn)是匹配情況一目了然.
譯者(acgtyrant)筆記
80 行限制事實(shí)上有助于避免代碼可讀性失控, 比如超多重嵌套塊, 超多重函數(shù)調(diào)用等等.
Linux 上設(shè)置好了 Locale 就幾乎一勞永逸設(shè)置好所有開(kāi)發(fā)環(huán)境的編碼, 不像奇葩的 Windows.
Google 強(qiáng)調(diào)有一對(duì) if-else 時(shí), 不論有沒(méi)有嵌套, 都要有大括號(hào). Apple 正好有栽過(guò)跟頭.
其實(shí)我主張指針/地址操作符與變量名緊鄰,int*a,bvsint*a,b, 新手會(huì)誤以為前者的b是int*變量, 但后者就不一樣了, 高下立判.
在這風(fēng)格指南里我才剛知道 C++ 原來(lái)還有所謂的Alternative operator representations, 大概沒(méi)人用吧.
注意構(gòu)造函數(shù)初始值列表(Constructer Initializer List)與列表初始化(Initializer List)是兩碼事, 我就差點(diǎn)混淆了它們的翻譯.
事實(shí)上, 如果您熟悉英語(yǔ)本身的書寫規(guī)則, 就會(huì)發(fā)現(xiàn)該風(fēng)格指南在格式上的規(guī)定與英語(yǔ)語(yǔ)法相當(dāng)一脈相承. 比如普通標(biāo)點(diǎn)符號(hào)和單詞后面還有文本的話, 總會(huì)留一個(gè)空格; 特殊符號(hào)與單詞之間就不用留了, 比如if(true)中的圓括號(hào)與true.
本風(fēng)格指南沒(méi)有明確規(guī)定 void 函數(shù)里要不要用 return 語(yǔ)句, 不過(guò)就 Google 開(kāi)源項(xiàng)目 leveldb 并沒(méi)有寫; 此外從Is a blank return statement at the end of a function whos return type is void necessary?來(lái)看,return;比return;更約定俗成(事實(shí)上 cpplint 會(huì)對(duì)后者報(bào)錯(cuò), 指出分號(hào)前有多余的空格), 且可用來(lái)提前跳出函數(shù)棧.
10. 規(guī)則特例
前面說(shuō)明的編程習(xí)慣基本都是強(qiáng)制性的. 但所有優(yōu)秀的規(guī)則都允許例外, 這里就是探討這些特例.
10.1. 現(xiàn)有不合規(guī)范的代碼
總述
對(duì)于現(xiàn)有不符合既定編程風(fēng)格的代碼可以網(wǎng)開(kāi)一面.
說(shuō)明
當(dāng)你修改使用其他風(fēng)格的代碼時(shí), 為了與代碼原有風(fēng)格保持一致可以不使用本指南約定. 如果不放心, 可以與代碼原作者或現(xiàn)在的負(fù)責(zé)人員商討. 記住,一致性也包括原有的一致性.
10.2. Windows 代碼
總述
Windows 程序員有自己的編程習(xí)慣, 主要源于 Windows 頭文件和其它 Microsoft 代碼. 我們希望任何人都可以順利讀懂你的代碼, 所以針對(duì)所有平臺(tái)的 C++ 編程只給出一個(gè)單獨(dú)的指南.
說(shuō)明
如果你習(xí)慣使用 Windows 編碼風(fēng)格, 這兒有必要重申一下某些你可能會(huì)忘記的指南:
不要使用匈牙利命名法 (比如把整型變量命名成iNum). 使用 Google 命名約定, 包括對(duì)源文件使用.cc擴(kuò)展名.
Windows 定義了很多原生類型的同義詞 (YuleFox 注: 這一點(diǎn), 我也很反感), 如DWORD,HANDLE等等. 在調(diào)用 Windows API 時(shí)這是完全可以接受甚至鼓勵(lì)的. 即使如此, 還是盡量使用原有的 C++ 類型, 例如使用constTCHAR*而不是LPCTSTR.
使用 Microsoft Visual C++ 進(jìn)行編譯時(shí), 將警告級(jí)別設(shè)置為 3 或更高, 并將所有警告(warnings)當(dāng)作錯(cuò)誤(errors)處理.
不要使用#pragmaonce; 而應(yīng)該使用 Google 的頭文件保護(hù)規(guī)則. 頭文件保護(hù)的路徑應(yīng)該相對(duì)于項(xiàng)目根目錄 (Yang.Y 注: 如#ifndefSRC_DIR_BAR_H_, 參考#define 保護(hù)一節(jié)).
除非萬(wàn)不得已, 不要使用任何非標(biāo)準(zhǔn)的擴(kuò)展, 如#pragma和__declspec. 使用__declspec(dllimport)和__declspec(dllexport)是允許的, 但必須通過(guò)宏來(lái)使用, 比如DLLIMPORT和DLLEXPORT, 這樣其他人在分享使用這些代碼時(shí)可以很容易地禁用這些擴(kuò)展.
然而, 在 Windows 上仍然有一些我們偶爾需要違反的規(guī)則:
通常我們禁止使用多重繼承, 但在使用 COM 和 ATL/WTL 類時(shí)可以使用多重繼承. 為了實(shí)現(xiàn) COM 或 ATL/WTL 類/接口, 你可能不得不使用多重實(shí)現(xiàn)繼承.
雖然代碼中不應(yīng)該使用異常, 但是在 ATL 和部分 STL(包括 Visual C++ 的 STL) 中異常被廣泛使用. 使用 ATL 時(shí), 應(yīng)定義_ATL_NO_EXCEPTIONS以禁用異常. 你需要研究一下是否能夠禁用 STL 的異常, 如果無(wú)法禁用, 可以啟用編譯器異常. (注意這只是為了編譯 STL, 自己的代碼里仍然不應(yīng)當(dāng)包含異常處理).
通常為了利用頭文件預(yù)編譯, 每個(gè)每個(gè)源文件的開(kāi)頭都會(huì)包含一個(gè)名為StdAfx.h或precompile.h的文件. 為了使代碼方便與其他項(xiàng)目共享, 請(qǐng)避免顯式包含此文件 (除了在precompile.cc中), 使用/FI編譯器選項(xiàng)以自動(dòng)包含該文件.
資源頭文件通常命名為resource.h且只包含宏, 這一文件不需要遵守本風(fēng)格指南.
11. 結(jié)束語(yǔ)
運(yùn)用常識(shí)和判斷力, 并且保持一致.
編輯代碼時(shí), 花點(diǎn)時(shí)間看看項(xiàng)目中的其它代碼, 并熟悉其風(fēng)格. 如果其它代碼中if語(yǔ)句使用空格, 那么你也要使用. 如果其中的注釋用星號(hào) (*) 圍成一個(gè)盒子狀, 那么你同樣要這么做.
風(fēng)格指南的重點(diǎn)在于提供一個(gè)通用的編程規(guī)范, 這樣大家可以把精力集中在實(shí)現(xiàn)內(nèi)容而不是表現(xiàn)形式上. 我們展示的是一個(gè)總體的的風(fēng)格規(guī)范, 但局部風(fēng)格也很重要, 如果你在一個(gè)文件中新加的代碼和原有代碼風(fēng)格相去甚遠(yuǎn), 這就破壞了文件本身的整體美觀, 也讓打亂讀者在閱讀代碼時(shí)的節(jié)奏, 所以要盡量避免.
好了, 關(guān)于編碼風(fēng)格寫的夠多了; 代碼本身才更有趣. 盡情享受吧!
-
Google
+關(guān)注
關(guān)注
5文章
1788瀏覽量
58700 -
編程
+關(guān)注
關(guān)注
88文章
3685瀏覽量
94915 -
C++
+關(guān)注
關(guān)注
22文章
2117瀏覽量
74823
原文標(biāo)題:Google C++ 編程規(guī)范 - 6
文章出處:【微信號(hào):C_Expert,微信公眾號(hào):C語(yǔ)言專家集中營(yíng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
MATLAB 編程風(fēng)格指南
Google C++編程指南
MATLAB編程風(fēng)格指南
Google編程風(fēng)格指南(一)
Google編程風(fēng)格指南(二)
Google編程風(fēng)格指南(三)
Google編程風(fēng)格指南(四)
Google編程風(fēng)格指南(五)
Google C++編程風(fēng)格指南PDF版免費(fèi)下載

Google C++編程風(fēng)格指南PDF電子書免費(fèi)下載

Verilog HIDL的RTL設(shè)計(jì)風(fēng)格指南資源下載
Google 應(yīng)用出海計(jì)劃 | 指南針 第六期強(qiáng)勢(shì)回歸啟動(dòng)報(bào)名!

評(píng)論