李開(kāi)復(fù)曾在微博上說(shuō)過(guò),Google的C++代碼規(guī)范是全球最好的一份C++代碼規(guī)范,沒(méi)有之一。最近花了點(diǎn)時(shí)間看了下這份代碼規(guī)范,收獲確實(shí)很大,在編程過(guò)程中一些亂七八糟的壞習(xí)慣也該改一改了。
下面幾個(gè)是我個(gè)人覺(jué)得收獲比較大的幾點(diǎn),要看完整版的,可以自己下載。
頭文件
函數(shù)參數(shù)順序
C/C++函數(shù)參數(shù)分為輸入?yún)?shù)和輸出參數(shù)兩種,有時(shí)輸入?yún)?shù)也會(huì)輸出(注:值被修改時(shí))。輸入?yún)?shù)一般傳值或常數(shù)引用(const references),輸出參數(shù)戒輸入/輸出參數(shù)為非常數(shù)指針(non-const pointers)。對(duì)參數(shù)排序時(shí),將所有輸入?yún)?shù)置于輸出參數(shù)之前。不要僅僅因?yàn)槭切绿砑拥膮?shù),就將其置于最后,而應(yīng)該依然置于輸出參數(shù)之前。這一點(diǎn)并不是必須遵循的規(guī)則,輸入/輸出兩用參數(shù)(通常是類(lèi)/結(jié)構(gòu)體變量)混在其中,會(huì)使得規(guī)則難以遵守。
個(gè)人感受:這條規(guī)則相當(dāng)重要,自己寫(xiě)代碼的時(shí)候可能沒(méi)有太大感覺(jué),但是在閱讀別人代碼的時(shí)候感覺(jué)特別明顯。如果代碼按照這種規(guī)范來(lái)寫(xiě),從某種角度來(lái)說(shuō),這段代碼具有“自注釋”的功能,那么在看代碼的時(shí)候就會(huì)比較輕松。Doom3的代碼規(guī)范中提到,“Use ‘const’ as much as possible”,也是同樣的意義。當(dāng)然,const除了閱讀方便以外,還有個(gè)很重要的就是防止編碼錯(cuò)誤,一旦在程序中修改const變量,編譯器就會(huì)報(bào)錯(cuò),這樣就減少了人工出錯(cuò)了可能性,這點(diǎn)尤為重要!
包含文件的名稱(chēng)及次序
將包含次序標(biāo)準(zhǔn)化可增強(qiáng)可讀性、避免隱藏依賴(lài)(hidden dependencies,注:隱藏依賴(lài)主要是指包含的文件編譯),次序如下:C 庫(kù)、C++庫(kù)、其他庫(kù)的.h、項(xiàng)目?jī)?nèi)的.h。
項(xiàng)目?jī)?nèi)頭文件應(yīng)挄照項(xiàng)目源代碼目錄樹(shù)結(jié)構(gòu)排列,并且避免使用UNIX文件路徑.(當(dāng)前目錄)和..(父目錄)。
舉例來(lái)說(shuō),google-awesome-project/src/foo/internal/fooserver.cc 的包含次序如下:#include "foo/public/fooserver.h" // 優(yōu)先位置#include
注意,對(duì)應(yīng)的頭文件一定要先包含,這樣避免隱藏依賴(lài),隱藏依賴(lài)的問(wèn)題不懂的可以去Google,網(wǎng)上有很多資料。另外,《C++編程思想》中提到的包含次序正好相反,從特殊到一般,但是有一點(diǎn)和Google代碼規(guī)范是一樣的,那就是對(duì)應(yīng)的頭文件是第一個(gè)包含。對(duì)于隱藏依賴(lài)的問(wèn)題,以前只是習(xí)慣性的把對(duì)應(yīng)的頭文件放第一個(gè),沒(méi)有想過(guò)為什么,現(xiàn)在學(xué)習(xí)了……
作用域
全局變量
class 類(lèi)型的全局變量是被禁止的,內(nèi)建類(lèi)型的全局變量是允許的,當(dāng)然多線(xiàn)程代碼中非常數(shù)全局變量也是被禁止的。永遠(yuǎn)不要使用函數(shù)返回值初始化全局變量。
不幸的是,全局變量的構(gòu)造函數(shù)、析構(gòu)函數(shù)以及初始化操作的調(diào)用順序只是被部分規(guī)定,每次生成有可能會(huì)有變化,從而導(dǎo)致難以發(fā)現(xiàn)bug。因此,禁止使用class類(lèi)型的全局變量(包括STL的string,vector等),因?yàn)樗鼈兊某跏蓟樞蚩赡軙?huì)導(dǎo)致出現(xiàn)問(wèn)題。內(nèi)建類(lèi)型和由內(nèi)建類(lèi)型構(gòu)成的沒(méi)有構(gòu)造函數(shù)的結(jié)構(gòu)體可以使用,如果你一定要使用class類(lèi)型的全局變量,請(qǐng)使用單件模式。
C++類(lèi)
構(gòu)造函數(shù)的職責(zé)
構(gòu)造函數(shù)中只進(jìn)行那些沒(méi)有實(shí)際意義的初始化,可能的話(huà),使用Init()方法集中初始化為有意義(non-trivial)的數(shù)據(jù)。
個(gè)人感受:這種做法可以從一開(kāi)始就避免一些bug的出現(xiàn),或更容易解決一些bug。構(gòu)造函數(shù)+Init()函數(shù)初始化的方式與只用構(gòu)造函數(shù)的方法相比,對(duì)計(jì)算機(jī)來(lái)說(shuō)他們是沒(méi)有區(qū)別的,但是人是會(huì)犯錯(cuò)的,這一條代碼規(guī)范在某種程度上避免了一些人為錯(cuò)誤,這個(gè)在開(kāi)發(fā)中特別重要。
拷貝構(gòu)造函數(shù)
僅在代碼中需要拷貝一個(gè)類(lèi)的對(duì)象的時(shí)候使用拷貝構(gòu)造函數(shù),不需要拷貝時(shí)使用DISALLOW_COPY_AND_ASSIGN這個(gè)宏(關(guān)于這個(gè)宏的內(nèi)容,可以在網(wǎng)上搜到,我這里就不寫(xiě)了)。C++中對(duì)象的隱式拷貝是導(dǎo)致很多性能問(wèn)題和bugs的根源。拷貝構(gòu)造函數(shù)降低了代碼可讀性,相比按引用傳遞,跟蹤按值傳遞的對(duì)象更加困難,對(duì)象修改的地方變得難以捉摸。
個(gè)人感受:和上一項(xiàng)的目的類(lèi)似,為了避免人為錯(cuò)誤!拷貝構(gòu)造函數(shù)本來(lái)是為了方便程序員編程了,但是卻有可能成為一個(gè)坑,為了避免這類(lèi)問(wèn)題,不需要拷貝時(shí)使用DISALLOW_COPY_AND_ASSIGN,這樣在需要調(diào)用拷貝構(gòu)造函數(shù)的時(shí)候就會(huì)報(bào)錯(cuò),減少了人為出錯(cuò)的可能性。C#和Java在這方面就做得比較好,雖然性能上不如C++,但是人為出錯(cuò)的概率減少了很多。當(dāng)然,使用一定的代碼規(guī)范,可以在一定程度上減少C++的坑。
繼承
雖然C++的繼承很好用,但是在實(shí)際開(kāi)發(fā)中,盡量多用組合少用繼承,不懂的去看GoF的《Design Patterns》。
但重定義派生的虛函數(shù)時(shí),在派生類(lèi)中明確聲明其為virtual。這一條是為了為了閱讀方便,雖然從語(yǔ)法的角度來(lái)說(shuō),在基類(lèi)中聲明了virtual,子類(lèi)可以不用再聲明該函數(shù)為virtual,但這樣一來(lái)閱讀代碼的人需要檢索類(lèi)的所有祖先以確定該函數(shù)是否為虛函數(shù)o(╯□╰)o。
多重繼承
雖然允許,但是只能一個(gè)基類(lèi)有實(shí)現(xiàn),其他基類(lèi)是接口,這樣一來(lái)和JAVA一樣了。這些東西在C#和JAVA中都進(jìn)行了改進(jìn),直接從語(yǔ)法上解決問(wèn)題。C++的靈活性過(guò)高,也是個(gè)麻煩的問(wèn)題,只能通過(guò)代碼規(guī)范填坑。
接口
虛基類(lèi)必須以Interface為后綴,方便閱讀。閱讀方便。
重載操作符
除少數(shù)特定情況外,不要重載操作符!!!“==”和“=”的操作Euqals和CopyFrom函數(shù)代替,這樣更直觀,也不容易出錯(cuò)。
個(gè)人感受:看到這一條,我有點(diǎn)驚訝,在學(xué)習(xí)C++的時(shí)候,說(shuō)重載操作符有神馬神馬好處,為什么現(xiàn)在又說(shuō)不要重載操作符呢?仔細(xì)看了他的文檔,確實(shí)說(shuō)的有道理,導(dǎo)致可能出現(xiàn)的bug見(jiàn)其具體文檔。在實(shí)際應(yīng)用中,由于C++的坑實(shí)在太多了,不得不把這種“好用”的東西干掉,因?yàn)槌隽薭ug又找不到,是一件很O疼的事情。
聲明次序
1)typedefs和enums;
2)常量;
3)構(gòu)造函數(shù);
4)析構(gòu)函數(shù);
5)成員函數(shù),含靜態(tài)成員函數(shù);
6)數(shù)據(jù)成員,含靜態(tài)數(shù)據(jù)成員。
宏 DISALLOW_COPY_AND_ASSIGN 置于private:塊之后,作為類(lèi)的最后部分。
其他C++特性
引用參數(shù)
函數(shù)形參表中,所有的引用必須的const!
個(gè)人感受:這么做是為了防止引用引起的誤解,因?yàn)橐迷谡Z(yǔ)法上是值,卻有指針的意義。雖然引用比較好用,但是犧牲其某些方面的特性,換來(lái)軟件管理方面的便利,還是很值得了。
缺省參數(shù)
禁止使用函數(shù)缺省參數(shù)!
個(gè)人感受:看到這一點(diǎn)的時(shí)候覺(jué)得有點(diǎn)因噎廢食了,其實(shí)缺省參數(shù)感覺(jué)還是蠻好用的。當(dāng)然從另外一個(gè)角度來(lái)說(shuō),要使用C++就不要怕這種小麻煩,如果因?yàn)槭褂眠@些特性造成了找不到的bug,那會(huì)損失更多時(shí)間。
異常
不要使用C++異常。
這一點(diǎn)我沒(méi)有看懂,也許是因?yàn)樗漠惓C(jī)制沒(méi)有C#和Java那么完善吧……畢竟在C#和Java里面異常還是很好用的東東。
流
除了記錄日志,不要使用流,使用printf之類(lèi)的代替。
這一條其實(shí)是有一些爭(zhēng)議的,當(dāng)然大多數(shù)人認(rèn)為代碼一致性比較重要,所以選擇printf,具體的可以看原文文檔。
const的使用
在任何可以的情況下都要使用const。
這條規(guī)則贊一個(gè),Doom3的代碼規(guī)范里也提到了這一條。這么做有兩個(gè)好處,一個(gè)是防止程序出錯(cuò),因?yàn)樾薷牧薱onst類(lèi)型的變量會(huì)報(bào)錯(cuò);另一個(gè)就是方便閱讀,使代碼“自注釋”。雖然這么做也有壞處,當(dāng)然,總體來(lái)說(shuō)利大于弊。
命名約定
1、總體規(guī)則:不要隨意縮寫(xiě),如果說(shuō) ChangeLocalValue 寫(xiě)作ChgLocVal還有情可原的話(huà),把ModifyPlayerName寫(xiě)作MdfPlyNm就太過(guò)分了,除函數(shù)名可適當(dāng)為動(dòng)詞外,其他命名盡量使用清晰易懂的名詞;
2、宏、枚舉等使用全部大寫(xiě)+下劃線(xiàn);
3、變量(含類(lèi)、結(jié)構(gòu)體成員變量)、文件、命名空間、存取函數(shù)等使用全部小寫(xiě)+下劃線(xiàn),類(lèi)成員變量以下劃線(xiàn)結(jié)尾,全局變量以g_開(kāi)頭;
4、普通函數(shù)、類(lèi)型(含類(lèi)與結(jié)構(gòu)體、枚舉類(lèi)型)、常量等使用大小寫(xiě)混合,不含下劃線(xiàn);
使用這套命名約定,可以使代碼具有一定程度的“自注釋”功能,方便他人閱讀,也方便自己以后修改。當(dāng)然3、4兩點(diǎn)也可以使用其他的命名約定,只要團(tuán)隊(duì)統(tǒng)一即可。
格式
1、行寬原則上不超過(guò)80列,把22寸的顯示屏都占完,怎么也說(shuō)不過(guò)去;
2、盡量不使用非ASCII字符,如果使用的話(huà),參考 UTF-8 格式(尤其是 UNIX/Linux 下,Windows 下可以考慮寬字符),盡量不將字符串常量耦合到代碼中,比如獨(dú)立出資源文件,返不僅僅是風(fēng)格問(wèn)題了;
3、UNIX/Linux下無(wú)條件使用空格,MSVC的話(huà)使用 Tab 也無(wú)可厚非; (我沒(méi)用過(guò)Linux,不懂為什么在Linux下無(wú)條件使用空格)
4、函數(shù)參數(shù)、邏輯條件、初始化列表:要么所有參數(shù)和函數(shù)名放在同一行,要么所有參數(shù)并排分行;
5、除函數(shù)定義的左大括號(hào)可以置于行首外,包括函數(shù)/類(lèi)/結(jié)極體/枚舉聲明、各種語(yǔ)句的左大括號(hào)置于行尾,所有右大括號(hào)獨(dú)立成行;
6、./->操作符前后丌留空格,*/&不要前后都留,一個(gè)就可,靠左靠右依各人喜好;
7、預(yù)處理指令/命名空間不使用額外縮進(jìn),類(lèi)/結(jié)構(gòu)體/枚舉/函數(shù)/語(yǔ)句使用縮進(jìn);
8、初始化用=還是()依個(gè)人喜好,統(tǒng)一就好;
9、return不要加();
10、水平/垂直留白不要濫用,怎么易讀怎么來(lái)。
寫(xiě)在最后
總的來(lái)說(shuō),這套代碼規(guī)范還是相當(dāng)不錯(cuò)的,既有防止錯(cuò)誤使用C++的某些特性而導(dǎo)致bugs的規(guī)范,又有代碼書(shū)寫(xiě)的相關(guān)規(guī)范使其便于閱讀,建議搞C++的童鞋都看一看。當(dāng)然,具體的團(tuán)隊(duì)?wèi)?yīng)該會(huì)有具體的代碼規(guī)范,代碼風(fēng)格方面大家可能會(huì)有一些區(qū)別;不使用C++某些特性(比如不使用C++異常,禁止使用函數(shù)缺省參數(shù))方面,應(yīng)該按照具體情況進(jìn)行折中處理,而不應(yīng)該生搬硬套代碼規(guī)范;但是“不將字符串常量耦合到代碼中”這種規(guī)范,是大家必須遵守的。
-
Google
+關(guān)注
關(guān)注
5文章
1758瀏覽量
57418 -
C++
+關(guān)注
關(guān)注
22文章
2104瀏覽量
73503 -
代碼
+關(guān)注
關(guān)注
30文章
4753瀏覽量
68368
原文標(biāo)題:閱讀Google的C++代碼規(guī)范有感
文章出處:【微信號(hào):C_Expert,微信公眾號(hào):C語(yǔ)言專(zhuān)家集中營(yíng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論