不錯(cuò)的資料,對(duì)寫程序有幫助
0.jpg (26.53 KB, 下載次數(shù): 59)
下載附件
2018-9-7 16:54 上傳
背景公司的開源項(xiàng)目大多使用C++開發(fā)。每一個(gè)C++程序員也都知道,C++具有很多強(qiáng)大的語言特性,但這種強(qiáng)大不可避免的導(dǎo)致它的復(fù)雜,這種復(fù)雜會(huì)使得代碼更易于出現(xiàn)bug、難于閱讀和維護(hù)。 本指南的目的是通過詳細(xì)闡述在C++編碼時(shí)要怎樣寫、不要怎樣寫來規(guī)避其復(fù)雜性。這些規(guī)則可在允許代碼有效使用C++語言特性的同時(shí)使其易于管理。 風(fēng)格,也被視為可讀性,主要指稱管理C++代碼的習(xí)慣。使用術(shù)語風(fēng)格有點(diǎn)用詞不當(dāng),因?yàn)檫@些習(xí)慣遠(yuǎn)不止源代碼文件格式這么簡(jiǎn)單。 使代碼易于管理的方法之一是增強(qiáng)代碼一致性,讓別人可以讀懂你的代碼是很重要的,保持統(tǒng)一編程風(fēng)格意味著可以輕松根據(jù)“模式匹配”規(guī)則推斷各種符號(hào)的含義。創(chuàng)建通用的、必需的習(xí)慣用語和模式可以使代碼更加容易理解,在某些情況下改變一些編程風(fēng)格可能會(huì)是好的選擇,但我們還是應(yīng)該遵循一致性原則,盡量不這樣去做。 本指南的另一個(gè)觀點(diǎn)是C++特性的臃腫。C++是一門包含大量高級(jí)特性的巨型語言,某些情況下,我們會(huì)限制甚至禁止使用某些特性使代碼簡(jiǎn)化,避免可能導(dǎo)致的各種問題,指南中列舉了這類特性,并解釋說為什么這些特性是被限制使用的。 由公司開發(fā)的開源項(xiàng)目將遵照本指南約定。 注意:本指南并非C++教程,我們假定讀者已經(jīng)對(duì)C++非常熟悉。 頭文件通常,每一個(gè).cc文件(C++的源文件)都有一個(gè)對(duì)應(yīng)的.h文件(頭文件),也有一些例外,如單元測(cè)試代碼和只包含main()的.cc文件。 正確使用頭文件可令代碼在可讀性、文件大小和性能上大為改觀。 下面的規(guī)則將引導(dǎo)你規(guī)避使用頭文件時(shí)的各種麻煩。 1. #define的保護(hù)所有頭文件都應(yīng)該使用#define防止頭文件被多重包含(multipleinclusion),命名格式當(dāng)是:<PROJECT>_<PATH>_<FILE>_H_ 為保證唯一性,頭文件的命名應(yīng)基于其所在項(xiàng)目源代碼樹的全路徑。例如,項(xiàng)目foo中的頭文件foo/src/bar/baz.h按如下方式保護(hù): #ifndef FOO_BAR_BAZ_H_ #define FOO_BAR_BAZ_H_ ... #endif // FOO_BAR_BAZ_H_ 2. 頭文件依賴使用前置聲明(forwarddeclarations)盡量減少.h文件中#include的數(shù)量。 當(dāng)一個(gè)頭文件被包含的同時(shí)也引入了一項(xiàng)新的依賴(dependency),只要該頭文件被修改,代碼就要重新編譯。如果你的頭文件包含了其他頭文件,這些頭文件的任何改變也將導(dǎo)致那些包含了你的頭文件的代碼重新編譯。因此,我們寧可盡量少包含頭文件,尤其是那些包含在其他頭文件中的。 使用前置聲明可以顯著減少需要包含的頭文件數(shù)量。舉例說明:頭文件中用到類File,但不需要訪問File的聲明,則頭文件中只需前置聲明class File;無需#include "file/base/file.h"。 在頭文件如何做到使用類Foo而無需訪問類的定義? 1) 將數(shù)據(jù)成員類型聲明為Foo *或Foo &; 2) 參數(shù)、返回值類型為Foo的函數(shù)只是聲明(但不定義實(shí)現(xiàn)); 3) 靜態(tài)數(shù)據(jù)成員的類型可以被聲明為Foo,因?yàn)殪o態(tài)數(shù)據(jù)成員的定義在類定義之外。 另一方面,如果你的類是Foo的子類,或者含有類型為Foo的非靜態(tài)數(shù)據(jù)成員,則必須為之包含頭文件。 有時(shí),使用指針成員(pointer members,如果是scoped_ptr更好)替代對(duì)象成員(object members)的確更有意義。然而,這樣的做法會(huì)降低代碼可讀性及執(zhí)行效率。如果僅僅為了少包含頭文件,還是不要這樣替代的好。 當(dāng)然,.cc文件無論如何都需要所使用類的定義部分,自然也就會(huì)包含若干頭文件。 譯者注:能依賴聲明的就不要依賴定義。 3. 內(nèi)聯(lián)函數(shù)只有當(dāng)函數(shù)只有10行甚至更少時(shí)才會(huì)將其定義為內(nèi)聯(lián)函數(shù)(inline function)。 定義(Definition):當(dāng)函數(shù)被聲明為內(nèi)聯(lián)函數(shù)之后,編譯器可能會(huì)將其內(nèi)聯(lián)展開,無需按通常的函數(shù)調(diào)用機(jī)制調(diào)用內(nèi)聯(lián)函數(shù)。 優(yōu)點(diǎn):當(dāng)函數(shù)體比較小的時(shí)候,內(nèi)聯(lián)該函數(shù)可以令目標(biāo)代碼更加高效。對(duì)于存取函數(shù)(accessor、mutator)以及其他一些比較短的關(guān)鍵執(zhí)行函數(shù)。 缺點(diǎn):濫用內(nèi)聯(lián)將導(dǎo)致程序變慢,內(nèi)聯(lián)有可能是目標(biāo)代碼量或增或減,這取決于被內(nèi)聯(lián)的函數(shù)的大小。內(nèi)聯(lián)較短小的存取函數(shù)通常會(huì)減少代碼量,但內(nèi)聯(lián)一個(gè)很大的函數(shù)(譯者注:如果編譯器允許的話)將戲劇性的增加代碼量。在現(xiàn)代處理器上,由于更好的利用指令緩存(instruction cache),小巧的代碼往往執(zhí)行更快。 結(jié)論:一個(gè)比較得當(dāng)?shù)奶幚硪?guī)則是,不要內(nèi)聯(lián)超過10行的函數(shù)。對(duì)于析構(gòu)函數(shù)應(yīng)慎重對(duì)待,析構(gòu)函數(shù)往往比其表面看起來要長(zhǎng),因?yàn)橛幸恍╇[式成員和基類析構(gòu)函數(shù)(如果有的話)被調(diào)用! 另一有用的處理規(guī)則:內(nèi)聯(lián)那些包含循環(huán)或switch語句的函數(shù)是得不償失的,除非在大多數(shù)情況下,這些循環(huán)或switch語句從不執(zhí)行。 重要的是,虛函數(shù)和遞歸函數(shù)即使被聲明為內(nèi)聯(lián)的也不一定就是內(nèi)聯(lián)函數(shù)。通常,遞歸函數(shù)不應(yīng)該被聲明為內(nèi)聯(lián)的(譯者注:遞歸調(diào)用堆棧的展開并不像循環(huán)那么簡(jiǎn)單,比如遞歸層數(shù)在編譯時(shí)可能是未知的,大多數(shù)編譯器都不支持內(nèi)聯(lián)遞歸函數(shù))。析構(gòu)函數(shù)內(nèi)聯(lián)的主要原因是其定義在類的定義中,為了方便抑或是對(duì)其行為給出文檔。 4. -inl.h文件復(fù)雜的內(nèi)聯(lián)函數(shù)的定義,應(yīng)放在后綴名為-inl.h的頭文件中。 在頭文件中給出內(nèi)聯(lián)函數(shù)的定義,可令編譯器將其在調(diào)用處內(nèi)聯(lián)展開。然而,實(shí)現(xiàn)代碼應(yīng)完全放到.cc文件中,我們不希望.h文件中出現(xiàn)太多實(shí)現(xiàn)代碼,除非這樣做在可讀性和效率上有明顯優(yōu)勢(shì)。 如果內(nèi)聯(lián)函數(shù)的定義比較短小、邏輯比較簡(jiǎn)單,其實(shí)現(xiàn)代碼可以放在.h文件中。例如,存取函數(shù)的實(shí)現(xiàn)理所當(dāng)然都放在類定義中。出于實(shí)現(xiàn)和調(diào)用的方便,較復(fù)雜的內(nèi)聯(lián)函數(shù)也可以放到.h文件中,如果你覺得這樣會(huì)使頭文件顯得笨重,還可以將其分離到單獨(dú)的-inl.h中。這樣即把實(shí)現(xiàn)和類定義分離開來,當(dāng)需要時(shí)包含實(shí)現(xiàn)所在的-inl.h即可。 -inl.h文件還可用于函數(shù)模板的定義,從而使得模板定義可讀性增強(qiáng)。 要提醒的一點(diǎn)是,-inl.h和其他頭文件一樣,也需要#define保護(hù)。 5. 函數(shù)參數(shù)順序(Function Parameter Ordering)定義函數(shù)時(shí),參數(shù)順序?yàn)椋狠斎雲(yún)?shù)在前,輸出參數(shù)在后。 C/C++函數(shù)參數(shù)分為輸入?yún)?shù)和輸出參數(shù)兩種,有時(shí)輸入?yún)?shù)也會(huì)輸出(譯者注:值被修改時(shí))。輸入?yún)?shù)一般傳值或常數(shù)引用(constreferences),輸出參數(shù)或輸入/輸出參數(shù)為非常數(shù)指針(non-const pointers)。對(duì)參數(shù)排序時(shí),將所有輸入?yún)?shù)置于輸出參數(shù)之前。不要僅僅因?yàn)槭切绿砑拥膮?shù),就將其置于最后,而應(yīng)該依然置于輸出參數(shù)之前。 這一點(diǎn)并不是必須遵循的規(guī)則,輸入/輸出兩用參數(shù)(通常是類/結(jié)構(gòu)體變量)混在其中,會(huì)使得規(guī)則難以遵循。 6. 包含文件的名稱及次序將包含次序標(biāo)準(zhǔn)化可增強(qiáng)可讀性、避免隱藏依賴(hiddendependencies,譯者注:隱藏依賴主要是指包含的文件中編譯時(shí)),次序如下:C庫、C++庫、其他庫的.h、項(xiàng)目?jī)?nèi)的.h。 項(xiàng)目?jī)?nèi)頭文件應(yīng)按照項(xiàng)目源代碼目錄樹結(jié)構(gòu)排列,并且避免使用UNIX文件路徑.(當(dāng)前目錄)和..(父目錄)。例如,公司-awesome-project/src/base/logging.h應(yīng)像這樣被包含: #include"base/logging.h" dir/foo.cc的主要作用是執(zhí)行或測(cè)試dir2/foo2.h的功能,foo.cc中包含頭文件的次序如下: dir2/foo2.h(優(yōu)先位置,詳情如下) C系統(tǒng)文件 C++系統(tǒng)文件 其他庫頭文件 本項(xiàng)目?jī)?nèi)頭文件 這種排序方式可有效減少隱藏依賴,我們希望每一個(gè)頭文件獨(dú)立編譯。最簡(jiǎn)單的實(shí)現(xiàn)方式是將其作為第一個(gè).h文件包含在對(duì)應(yīng)的.cc中。 dir/foo.cc和dir2/foo2.h通常位于相同目錄下(像base/basictypes_unittest.cc和base/basictypes.h),但也可在不同目錄下。 相同目錄下頭文件按字母序是不錯(cuò)的選擇。 舉例來說,公司-awesome-project/src/foo/internal/fooserver.cc的包含次序如下: #include "foo/public/fooserver.h" // 優(yōu)先位置 #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" ______________________________________ 譯者:英語不太好,翻譯的也就不太好。這一篇主要提到的是頭文件的一些規(guī)則,總結(jié)一下: 1. 避免多重包含是學(xué)編程時(shí)最基本的要求; 2. 前置聲明是為了降低編譯依賴,防止修改一個(gè)頭文件引發(fā)多米諾效應(yīng); 3. 內(nèi)聯(lián)函數(shù)的合理使用可提高代碼執(zhí)行效率; 4. -inl.h可提高代碼可讀性(一般用不到吧:D); 5. 標(biāo)準(zhǔn)化函數(shù)參數(shù)順序可以提高可讀性和易維護(hù)性(對(duì)函數(shù)參數(shù)的堆?臻g有輕微影響,我以前大多是相同類型放在一起); 6. 包含文件的名稱使用.和..雖然方便卻易混亂,使用比較完整的項(xiàng)目路徑看上去很清晰、很條理,包含文件的次序除了美觀之外,最重要的是可以減少隱藏依賴,使每個(gè)頭文件在“最需要編譯”(對(duì)應(yīng)源文件處:D)的地方編譯,有人提出庫文件放在最后,這樣出錯(cuò)先是項(xiàng)目?jī)?nèi)的文件,頭文件都放在對(duì)應(yīng)源文件的最前面,這一點(diǎn)足以保證內(nèi)部錯(cuò)誤的及時(shí)發(fā)現(xiàn)了。 公司 C++編程風(fēng)格指南(二)作用域1. 命名空間(Namespaces)在.cc文件中,提倡使用不具名的命名空間(unnamednamespaces,譯者注:不具名的命名空間就像不具名的類一樣,似乎被介紹的很少:-()。使用具名命名空間時(shí),其名稱可基于項(xiàng)目或路徑名稱,不要使用using指示符。 定義:命名空間將全局作用域細(xì)分為不同的、具名的作用域,可有效防止全局作用域的命名沖突。 優(yōu)點(diǎn):命名空間提供了(可嵌套)命名軸線(name axis,譯者注:將命名分割在不同命名空間內(nèi)),當(dāng)然,類也提供了(可嵌套)的命名軸線(譯者注:將命名分割在不同類的作用域內(nèi))。 舉例來說,兩個(gè)不同項(xiàng)目的全局作用域都有一個(gè)類Foo,這樣在編譯或運(yùn)行時(shí)造成沖突。如果每個(gè)項(xiàng)目將代碼置于不同命名空間中,project1::Foo和project2::Foo作為不同符號(hào)自然不會(huì)沖突。 缺點(diǎn):命名空間具有迷惑性,因?yàn)樗鼈兒皖愐粯犹峁┝祟~外的(可嵌套的)命名軸線。在頭文件中使用不具名的空間容易違背C++的唯一定義原則(One Definition Rule (ODR))。 結(jié)論:根據(jù)下文將要提到的策略合理使用命名空間。 1) 不具名命名空間(Unnamed Namespaces)在.cc文件中,允許甚至提倡使用不具名命名空間,以避免運(yùn)行時(shí)的命名沖突: namespace{ // .cc 文件中 // 命名空間的內(nèi)容無需縮進(jìn) enum { UNUSED, EOF, ERROR}; // 經(jīng)常使用的符號(hào) bool AtEof() { return pos_ == EOF; } // 使用本命名空間內(nèi)的符號(hào)EOF } // namespace 然而,與特定類關(guān)聯(lián)的文件作用域聲明在該類中被聲明為類型、靜態(tài)數(shù)據(jù)成員或靜態(tài)成員函數(shù),而不是不具名命名空間的成員。像上文展示的那樣,不具名命名空間結(jié)束時(shí)用注釋// namespace標(biāo)識(shí)。 不能在.h文件中使用不具名命名空間。 2) 具名命名空間(Named Namespaces)具名命名空間使用方式如下: 命名空間將除文件包含、全局標(biāo)識(shí)的聲明/定義以及類的前置聲明外的整個(gè)源文件封裝起來,以同其他命名空間相區(qū)分。 // .h文件 namespace mynamespace { // 所有聲明都置于命名空間中 // 注意不要使用縮進(jìn) class MyClass { public: ... void Foo(); }; } // namespace mynamespace // .cc文件 namespace mynamespace { // 函數(shù)定義都置于命名空間中 void MyClass::Foo() { ... } } // namespace mynamespace 通常的.cc文件會(huì)包含更多、更復(fù)雜的細(xì)節(jié),包括對(duì)其他命名空間中類的引用等。 #include "a.h" DEFINE_bool(someflag, false, "dummy flag"); class C; // 全局命名空間中類C的前置聲明 namespace a { class A; } // 命名空間a中的類a::A的前置聲明 namespace b { ...code forb... // b中的代碼 } // namespace b 不要聲明命名空間std下的任何內(nèi)容,包括標(biāo)準(zhǔn)庫類的前置聲明。聲明std下的實(shí)體會(huì)導(dǎo)致不明確的行為,如,不可移植。聲明標(biāo)準(zhǔn)庫下的實(shí)體,需要包含對(duì)應(yīng)的頭文件。 最好不要使用using指示符,以保證命名空間下的所有名稱都可以正常使用。 // 禁止——污染命名空間 using namespace foo; 在.cc文件、.h文件的函數(shù)、方法或類中,可以使用using。 // 允許:.cc文件中 // .h文件中,必須在函數(shù)、方法或類的內(nèi)部使用 using ::foo::bar; 在.cc文件、.h文件的函數(shù)、方法或類中,還可以使用命名空間別名。 // 允許:.cc文件中 // .h文件中,必須在函數(shù)、方法或類的內(nèi)部使用 namespace fbz = ::foo::bar::baz; 2. 嵌套類(Nested Class)當(dāng)公開嵌套類作為接口的一部分時(shí),雖然可以直接將他們保持在全局作用域中,但將嵌套類的聲明置于命名空間中是更好的選擇。 定義:可以在一個(gè)類中定義另一個(gè)類,嵌套類也稱成員類(member class)。 class Foo { private: // Bar是嵌套在Foo中的成員類 class Bar { ... }; }; 優(yōu)點(diǎn):當(dāng)嵌套(成員)類只在被嵌套類(enclosing class)中使用時(shí)很有用,將其置于被嵌套類作用域作為被嵌套類的成員不會(huì)污染其他作用域同名類?稍诒磺短最愔星爸寐暶髑短最,在.cc文件中定義嵌套類,避免在被嵌套類中包含嵌套類的定義,因?yàn)榍短最惖亩x通常只與實(shí)現(xiàn)相關(guān)。 缺點(diǎn):只能在被嵌套類的定義中才能前置聲明嵌套類。因此,任何使用Foo::Bar*指針的頭文件必須包含整個(gè)Foo的聲明。 結(jié)論:不要將嵌套類定義為public,除非它們是接口的一部分,比如,某個(gè)方法使用了這個(gè)類的一系列選項(xiàng)。 3. 非成員函數(shù)(Nonmember)、靜態(tài)成員函數(shù)(Static Member)和全局函數(shù)(GlobalFunctions)使用命名空間中的非成員函數(shù)或靜態(tài)成員函數(shù),盡量不要使用全局函數(shù)。 優(yōu)點(diǎn):某些情況下,非成員函數(shù)和靜態(tài)成員函數(shù)是非常有用的,將非成員函數(shù)置于命名空間中可避免對(duì)全局作用域的污染。 缺點(diǎn):將非成員函數(shù)和靜態(tài)成員函數(shù)作為新類的成員或許更有意義,當(dāng)它們需要訪問外部資源或具有重要依賴時(shí)更是如此。 結(jié)論: 有時(shí),不把函數(shù)限定在類的實(shí)體中是有益的,甚至需要這么做,要么作為靜態(tài)成員,要么作為非成員函數(shù)。非成員函數(shù)不應(yīng)依賴于外部變量,并盡量置于某個(gè)命名空間中。相比單純?yōu)榱朔庋b若干不共享任何靜態(tài)數(shù)據(jù)的靜態(tài)成員函數(shù)而創(chuàng)建類,不如使用命名空間。 定義于同一編譯單元的函數(shù),被其他編譯單元直接調(diào)用可能會(huì)引入不必要的耦合和連接依賴;靜態(tài)成員函數(shù)對(duì)此尤其敏感?梢钥紤]提取到新類中,或者將函數(shù)置于獨(dú)立庫的命名空間中。 如果你確實(shí)需要定義非成員函數(shù),又只是在.cc文件中使用它,可使用不具名命名空間或static關(guān)聯(lián)(如static int Foo() {...})限定其作用域。 4. 局部變量(Local Variables)將函數(shù)變量盡可能置于最小作用域內(nèi),在聲明變量時(shí)將其初始化。 C++允許在函數(shù)的任何位置聲明變量。我們提倡在盡可能小的作用域中聲明變量,離第一次使用越近越好。這使得代碼易于閱讀,易于定位變量的聲明位置、變量類型和初始值。特別是,應(yīng)使用初始化代替聲明+賦值的方式。 int i; i = f(); // 壞——初始化和聲明分離 nt j = g(); // 好——初始化時(shí)聲明 注意:gcc可正確執(zhí)行for (int i = 0; i < 10;++i)(i的作用域僅限for循環(huán)),因此其他for循環(huán)中可重用i。if和while等語句中,作用域聲明(scope declaration)同樣是正確的。 while (const char* p = strchr(str, '/')) str = p + 1; 注意:如果變量是一個(gè)對(duì)象,每次進(jìn)入作用域都要調(diào)用其構(gòu)造函數(shù),每次退出作用域都要調(diào)用其析構(gòu)函數(shù)。 // 低效的實(shí)現(xiàn) for (int i = 0; i < 1000000; ++i) { Foo f; // 構(gòu)造函數(shù)和析構(gòu)函數(shù)分別調(diào)用1000000次! f.DoSomething(i); } 類似變量放到循環(huán)作用域外面聲明要高效的多: Foo f; // 構(gòu)造函數(shù)和析構(gòu)函數(shù)只調(diào)用1次 for (int i = 0; i < 1000000; ++i) { f.DoSomething(i); } 5. 全局變量(Global Variables)class類型的全局變量是被禁止的,內(nèi)建類型的全局變量是允許的,當(dāng)然多線程代碼中非常數(shù)全局變量也是被禁止的。永遠(yuǎn)不要使用函數(shù)返回值初始化全局變量。 不幸的是,全局變量的構(gòu)造函數(shù)、析構(gòu)函數(shù)以及初始化操作的調(diào)用順序只是被部分規(guī)定,每次生成有可能會(huì)有變化,從而導(dǎo)致難以發(fā)現(xiàn)的bugs。 因此,禁止使用class類型的全局變量(包括STL的string, vector等等),因?yàn)樗鼈兊某跏蓟樞蛴锌赡軐?dǎo)致構(gòu)造出現(xiàn)問題。內(nèi)建類型和由內(nèi)建類型構(gòu)成的沒有構(gòu)造函數(shù)的結(jié)構(gòu)體可以使用,如果你一定要使用class類型的全局變量,請(qǐng)使用單件模式(singleton pattern)。 對(duì)于全局的字符串常量,使用C風(fēng)格的字符串,而不要使用STL的字符串: const char kFrogSays[] = "ribbet"; 雖然允許在全局作用域中使用全局變量,使用時(shí)務(wù)必三思。大多數(shù)全局變量應(yīng)該是類的靜態(tài)數(shù)據(jù)成員,或者當(dāng)其只在.cc文件中使用時(shí),將其定義到不具名命名空間中,或者使用靜態(tài)關(guān)聯(lián)以限制變量的作用域。 記住,靜態(tài)成員變量視作全局變量,所以,也不能是class類型! ______________________________________ 譯者:這一篇主要提到的是作用域的一些規(guī)則,總結(jié)一下: 1. .cc中的不具名命名空間可避免命名沖突、限定作用域,避免直接使用using提示符污染命名空間; 2. 嵌套類符合局部使用原則,只是不能在其他頭文件中前置聲明,盡量不要public; 3. 盡量不用全局函數(shù)和全局變量,考慮作用域和命名空間限制,盡量單獨(dú)形成編譯單元; 4. 多線程中的全局變量(含靜態(tài)成員變量)不要使用class類型(含STL容器),避免不明確行為導(dǎo)致的bugs。 作用域的使用,除了考慮名稱污染、可讀性之外,主要是為降低耦合度,提高編譯、執(zhí)行效率。 公司 C++編程風(fēng)格指南(三)這一篇主要提到的是類,Lippman在《Inside The C++ Object Model》第二章中對(duì)構(gòu)造函數(shù)作了詳盡說明,本文中提到的幾個(gè)單詞基本仿該書中譯本侯捷先生的翻譯: explicit:明確的 implicit:隱含的 trivial:沒有意義的 non-trivial:有意義的
原文地址:公司 C++ Style Guide 類類是C++中基本的代碼單元,自然被廣泛使用。本節(jié)列舉了在寫一個(gè)類時(shí)要做什么、不要做什么。 1. 構(gòu)造函數(shù)(Constructor)的職責(zé)構(gòu)造函數(shù)中只進(jìn)行那些沒有實(shí)際意義的(trivial,譯者注:簡(jiǎn)單初始化對(duì)于程序執(zhí)行沒有實(shí)際的邏輯意義,因?yàn)槌蓡T變量的“有意義”的值大多不在構(gòu)造函數(shù)中確定)初始化,可能的話,使用Init()方法集中初始化為有意義的(non-trivial)數(shù)據(jù)。 定義:在構(gòu)造函數(shù)中執(zhí)行初始化操作。 優(yōu)點(diǎn):排版方便,無需擔(dān)心類是否初始化。 缺點(diǎn):在構(gòu)造函數(shù)中執(zhí)行操作引起的問題有: 1) 構(gòu)造函數(shù)中不易報(bào)告錯(cuò)誤,不能使用異常。 2) 操作失敗會(huì)造成對(duì)象初始化失敗,引起不確定狀態(tài)。 3) 構(gòu)造函數(shù)內(nèi)調(diào)用虛函數(shù),調(diào)用不會(huì)派發(fā)到子類實(shí)現(xiàn)中,即使當(dāng)前沒有子類化實(shí)現(xiàn),將來仍是隱患。 4) 如果有人創(chuàng)建該類型的全局變量(雖然違背了上節(jié)提到的規(guī)則),構(gòu)造函數(shù)將在main()之前被調(diào)用,有可能破壞構(gòu)造函數(shù)中暗含的假設(shè)條件。例如,gflags尚未初始化。 結(jié)論:如果對(duì)象需要有意義的(non-trivial)初始化,考慮使用另外的Init()方法并(或)增加一個(gè)成員標(biāo)記用于指示對(duì)象是否已經(jīng)初始化成功。 2. 默認(rèn)構(gòu)造函數(shù)(Default Constructors)如果一個(gè)類定義了若干成員變量又沒有其他構(gòu)造函數(shù),需要定義一個(gè)默認(rèn)構(gòu)造函數(shù),否則編譯器將自動(dòng)生產(chǎn)默認(rèn)構(gòu)造函數(shù)。 定義:新建一個(gè)沒有參數(shù)的對(duì)象時(shí),默認(rèn)構(gòu)造函數(shù)被調(diào)用,當(dāng)調(diào)用new[](為數(shù)組)時(shí),默認(rèn)構(gòu)造函數(shù)總是被調(diào)用。 優(yōu)點(diǎn):默認(rèn)將結(jié)構(gòu)體初始化為“不可能的”值,使調(diào)試更加容易。 缺點(diǎn):對(duì)代碼編寫者來說,這是多余的工作。 結(jié)論: 如果類中定義了成員變量,沒有提供其他構(gòu)造函數(shù),你需要定義一個(gè)默認(rèn)構(gòu)造函數(shù)(沒有參數(shù))。默認(rèn)構(gòu)造函數(shù)更適合于初始化對(duì)象,使對(duì)象內(nèi)部狀態(tài)(internal state)一致、有效。 提供默認(rèn)構(gòu)造函數(shù)的原因是:如果你沒有提供其他構(gòu)造函數(shù),又沒有定義默認(rèn)構(gòu)造函數(shù),編譯器將為你自動(dòng)生成一個(gè),編譯器生成的構(gòu)造函數(shù)并不會(huì)對(duì)對(duì)象進(jìn)行初始化。 如果你定義的類繼承現(xiàn)有類,而你又沒有增加新的成員變量,則不需要為新類定義默認(rèn)構(gòu)造函數(shù)。 3. 明確的構(gòu)造函數(shù)(Explicit Constructors)對(duì)單參數(shù)構(gòu)造函數(shù)使用C++關(guān)鍵字explicit。 定義:通常,只有一個(gè)參數(shù)的構(gòu)造函數(shù)可被用于轉(zhuǎn)換(conversion,譯者注:主要指隱式轉(zhuǎn)換,下文可見),例如,定義了Foo::Foo(string name),當(dāng)向需要傳入一個(gè)Foo對(duì)象的函數(shù)傳入一個(gè)字符串時(shí),構(gòu)造函數(shù)Foo::Foo(string name)被調(diào)用并將該字符串轉(zhuǎn)換為一個(gè)Foo臨時(shí)對(duì)象傳給調(diào)用函數(shù)。看上去很方便,但如果你并不希望如此通過轉(zhuǎn)換生成一個(gè)新對(duì)象的話,麻煩也隨之而來。為避免構(gòu)造函數(shù)被調(diào)用造成隱式轉(zhuǎn)換,可以將其聲明為explicit。 優(yōu)點(diǎn):避免不合時(shí)宜的變換。 缺點(diǎn):無。 結(jié)論: 所有單參數(shù)構(gòu)造函數(shù)必須是明確的。在類定義中,將關(guān)鍵字explicit加到單參數(shù)構(gòu)造函數(shù)前:explicit Foo(string name); 例外:在少數(shù)情況下,拷貝構(gòu)造函數(shù)可以不聲明為explicit;特意作為其他類的透明包裝器的類。類似例外情況應(yīng)在注釋中明確說明。 4. 拷貝構(gòu)造函數(shù)(Copy Constructors)僅在代碼中需要拷貝一個(gè)類對(duì)象的時(shí)候使用拷貝構(gòu)造函數(shù);不需要拷貝時(shí)應(yīng)使用DISALLOW_COPY_AND_ASSIGN。 定義:通過拷貝新建對(duì)象時(shí)可使用拷貝構(gòu)造函數(shù)(特別是對(duì)象的傳值時(shí))。 優(yōu)點(diǎn):拷貝構(gòu)造函數(shù)使得拷貝對(duì)象更加容易,STL容器要求所有內(nèi)容可拷貝、可賦值。 缺點(diǎn):C++中對(duì)象的隱式拷貝是導(dǎo)致很多性能問題和bugs的根源。拷貝構(gòu)造函數(shù)降低了代碼可讀性,相比按引用傳遞,跟蹤按值傳遞的對(duì)象更加困難,對(duì)象修改的地方變得難以捉摸。 結(jié)論: 大量的類并不需要可拷貝,也不需要一個(gè)拷貝構(gòu)造函數(shù)或賦值操作(assignmentoperator)。不幸的是,如果你不主動(dòng)聲明它們,編譯器會(huì)為你自動(dòng)生成,而且是public的。 可以考慮在類的private中添加空的(dummy)拷貝構(gòu)造函數(shù)和賦值操作,只有聲明,沒有定義。由于這些空程序聲明為private,當(dāng)其他代碼試圖使用它們的時(shí)候,編譯器將報(bào)錯(cuò)。為了方便,可以使用宏DISALLOW_COPY_AND_ASSIGN: // 禁止使用拷貝構(gòu)造函數(shù)和賦值操作的宏 // 應(yīng)在類的private:中使用 #defineDISALLOW_COPY_AND_ASSIGN(TypeName) \ TypeName(constTypeName&); \ void operator=(const TypeName&) class Foo { public: Foo(int f); ~Foo(); private: DISALLOW_COPY_AND_ASSIGN(Foo); }; 如上所述,絕大多數(shù)情況下都應(yīng)使用DISALLOW_COPY_AND_ASSIGN,如果類確實(shí)需要可拷貝,應(yīng)在該類的頭文件中說明原由,并適當(dāng)定義拷貝構(gòu)造函數(shù)和賦值操作,注意在operator=中檢測(cè)自賦值(self-assignment)情況。 在將類作為STL容器值得時(shí)候,你可能有使類可拷貝的沖動(dòng)。類似情況下,真正該做的是使用指針指向STL容器中的對(duì)象,可以考慮使用std::tr1::shared_ptr。 5. 結(jié)構(gòu)體和類(Structs vs. Classes)僅當(dāng)只有數(shù)據(jù)時(shí)使用struct,其它一概使用class。 在C++中,關(guān)鍵字struct和class幾乎含義等同,我們?yōu)槠淙藶樘砑诱Z義,以便為定義的數(shù)據(jù)類型合理選擇使用哪個(gè)關(guān)鍵字。 struct被用在僅包含數(shù)據(jù)的消極對(duì)象(passiveobjects)上,可能包括有關(guān)聯(lián)的常量,但沒有存取數(shù)據(jù)成員之外的函數(shù)功能,而存取功能通過直接訪問實(shí)現(xiàn)而無需方法調(diào)用,這兒提到的方法是指只用于處理數(shù)據(jù)成員的,如構(gòu)造函數(shù)、析構(gòu)函數(shù)、Initialize()、Reset()、Validate()。 如果需要更多的函數(shù)功能,class更適合,如果不確定的話,直接使用class。 如果與STL結(jié)合,對(duì)于仿函數(shù)(functors)和特性(traits)可以不用class而是使用struct。 注意:類和結(jié)構(gòu)體的成員變量使用不同的命名規(guī)則。 6. 繼承(Inheritance)使用組合(composition,譯者注,這一點(diǎn)也是GoF在《Design Patterns》里反復(fù)強(qiáng)調(diào)的)通常比使用繼承更適宜,如果使用繼承的話,只使用公共繼承。 定義:當(dāng)子類繼承基類時(shí),子類包含了父基類所有數(shù)據(jù)及操作的定義。C++實(shí)踐中,繼承主要用于兩種場(chǎng)合:實(shí)現(xiàn)繼承(implementation inheritance),子類繼承父類的實(shí)現(xiàn)代碼;接口繼承(interface inheritance),子類僅繼承父類的方法名稱。 優(yōu)點(diǎn):實(shí)現(xiàn)繼承通過原封不動(dòng)的重用基類代碼減少了代碼量。由于繼承是編譯時(shí)聲明(compile-time declaration),編碼者和編譯器都可以理解相應(yīng)操作并發(fā)現(xiàn)錯(cuò)誤。接口繼承可用于程序上增強(qiáng)類的特定API的功能,在類沒有定義API的必要實(shí)現(xiàn)時(shí),編譯器同樣可以偵錯(cuò)。 缺點(diǎn):對(duì)于實(shí)現(xiàn)繼承,由于實(shí)現(xiàn)子類的代碼在父類和子類間延展,要理解其實(shí)現(xiàn)變得更加困難。子類不能重寫父類的非虛函數(shù),當(dāng)然也就不能修改其實(shí)現(xiàn)。基類也可能定義了一些數(shù)據(jù)成員,還要區(qū)分基類的物理輪廓(physical layout)。 結(jié)論: 所有繼承必須是public的,如果想私有繼承的話,應(yīng)該采取包含基類實(shí)例作為成員的方式作為替代。 不要過多使用實(shí)現(xiàn)繼承,組合通常更合適一些。努力做到只在“是一個(gè)”("is-a",譯者注,其他"has-a"情況下請(qǐng)使用組合)的情況下使用繼承:如果Bar的確“是一種”Foo,才令Bar是Foo的子類。 必要的話,令析構(gòu)函數(shù)為virtual,必要是指,如果該類具有虛函數(shù),其析構(gòu)函數(shù)應(yīng)該為虛函數(shù)。 譯者注:至于子類沒有額外數(shù)據(jù)成員,甚至父類也沒有任何數(shù)據(jù)成員的特殊情況下,析構(gòu)函數(shù)的調(diào)用是否必要是語義爭(zhēng)論,從編程設(shè)計(jì)規(guī)范的角度看,在含有虛函數(shù)的父類中,定義虛析構(gòu)函數(shù)絕對(duì)必要。 限定僅在子類訪問的成員函數(shù)為protected,需要注意的是數(shù)據(jù)成員應(yīng)始終為私有。 當(dāng)重定義派生的虛函數(shù)時(shí),在派生類中明確聲明其為virtual。根本原因:如果遺漏virtual,閱讀者需要檢索類的所有祖先以確定該函數(shù)是否為虛函數(shù)(譯者注,雖然不影響其為虛函數(shù)的本質(zhì))。 7. 多重繼承(Multiple Inheritance)真正需要用到多重實(shí)現(xiàn)繼承(multipleimplementation inheritance)的時(shí)候非常少,只有當(dāng)最多一個(gè)基類中含有實(shí)現(xiàn),其他基類都是以Interface為后綴的純接口類時(shí)才會(huì)使用多重繼承。 定義:多重繼承允許子類擁有多個(gè)基類,要將作為純接口的基類和具有實(shí)現(xiàn)的基類區(qū)別開來。 優(yōu)點(diǎn):相比單繼承,多重實(shí)現(xiàn)繼承可令你重用更多代碼。 缺點(diǎn):真正需要用到多重實(shí)現(xiàn)繼承的時(shí)候非常少,多重實(shí)現(xiàn)繼承看上去是不錯(cuò)的解決方案,通?梢哉业礁用鞔_、清晰的、不同的解決方案。 結(jié)論:只有當(dāng)所有超類(superclass)除第一個(gè)外都是純接口時(shí)才能使用多重繼承。為確保它們是純接口,這些類必須以Interface為后綴。 注意:關(guān)于此規(guī)則,Windows下有種例外情況(譯者注,將在本譯文最后一篇的規(guī)則例外中闡述)。 8. 接口(Interface)接口是指滿足特定條件的類,這些類以Interface為后綴(非必需)。 定義:當(dāng)一個(gè)類滿足以下要求時(shí),稱之為純接口: 1) 只有純虛函數(shù)("=0")和靜態(tài)函數(shù)(下文提到的析構(gòu)函數(shù)除外); 2) 沒有非靜態(tài)數(shù)據(jù)成員; 3) 沒有定義任何構(gòu)造函數(shù)。如果有,也不含參數(shù),并且為protected; 4) 如果是子類,也只能繼承滿足上述條件并以Interface為后綴的類。 接口類不能被直接實(shí)例化,因?yàn)樗暶髁思兲摵瘮?shù)。為確保接口類的所有實(shí)現(xiàn)可被正確銷毀,必須為之聲明虛析構(gòu)函數(shù)(作為第1條規(guī)則的例外,析構(gòu)函數(shù)不能是純虛函數(shù))。具體細(xì)節(jié)可參考Stroustrup的《The C++Programming Language, 3rd edition》第12.4節(jié)。 優(yōu)點(diǎn):以Interface為后綴可令他人知道不能為該接口類增加實(shí)現(xiàn)函數(shù)或非靜態(tài)數(shù)據(jù)成員,這一點(diǎn)對(duì)于多重繼承尤其重要。另外,對(duì)于Java程序員來說,接口的概念已經(jīng)深入人心。 缺點(diǎn):Interface后綴增加了類名長(zhǎng)度,為閱讀和理解帶來不便,同時(shí),接口特性作為實(shí)現(xiàn)細(xì)節(jié)不應(yīng)暴露給客戶。 結(jié)論:。只有在滿足上述需要時(shí),類才以Interface結(jié)尾,但反過來,滿足上述需要的類未必一定以Interface結(jié)尾。 9. 操作符重載(Operator Overloading)除少數(shù)特定環(huán)境外,不要重載操作符。 定義:一個(gè)類可以定義諸如+、/等操作符,使其可以像內(nèi)建類型一樣直接使用。 優(yōu)點(diǎn):使代碼看上去更加直觀,就像內(nèi)建類型(如int)那樣,重載操作符使那些Equals()、Add()等黯淡無光的函數(shù)名好玩多了。為了使一些模板函數(shù)正確工作,你可能需要定義操作符。 缺點(diǎn):雖然操作符重載令代碼更加直觀,但也有一些不足 1) 混淆直覺,讓你誤以為一些耗時(shí)的操作像內(nèi)建操作那樣輕巧; 2) 查找重載操作符的調(diào)用處更加困難,查找Equals()顯然比同等調(diào)用==容易的多; 3) 有的操作符可以對(duì)指針進(jìn)行操作,容易導(dǎo)致bugs,F(xiàn)oo + 4做的是一件事,而&Foo + 4可能做的是完全不同的另一件事,對(duì)于二者,編譯器都不會(huì)報(bào)錯(cuò),使其很難調(diào)試; 4) 重載還有令你吃驚的副作用,比如,重載操作符&的類不能被前置聲明。 結(jié)論: 一般不要重載操作符,尤其是賦值操作(operator=)比較陰險(xiǎn),應(yīng)避免重載。如果需要的話,可以定義類似Equals()、CopyFrom()等函數(shù)。 然而,極少數(shù)情況下需要重載操作符以便與模板或“標(biāo)準(zhǔn)”C++類銜接(如operator<<(ostream&,const T&)),如果被證明是正當(dāng)?shù)纳锌山邮,但你要盡可能避免這樣做。尤其是不要僅僅為了在STL容器中作為key使用就重載operator==或operator<,取而代之,你應(yīng)該在聲明容器的時(shí)候,創(chuàng)建相等判斷和大小比較的仿函數(shù)類型。 有些STL算法確實(shí)需要重載operator==時(shí)可以這么做,不要忘了提供文檔說明原因。 參考拷貝構(gòu)造函數(shù)和函數(shù)重載。 10. 存取控制(Access Control)將數(shù)據(jù)成員私有化,并提供相關(guān)存取函數(shù),如定義變量foo_及取值函數(shù)foo()、賦值函數(shù)set_foo()。 存取函數(shù)的定義一般內(nèi)聯(lián)在頭文件中。 參考繼承和函數(shù)命名。 11. 聲明次序(Declaration Order)在類中使用特定的聲明次序:public:在private:之前,成員函數(shù)在數(shù)據(jù)成員(變量)前。 定義次序如下:public:、protected:、private:,如果那一塊沒有,直接忽略即可。 每一塊中,聲明次序一般如下: 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:塊之后,作為類的最后部分。參考拷貝構(gòu)造函數(shù)。 .cc文件中函數(shù)的定義應(yīng)盡可能和聲明次序一致。 不要將大型函數(shù)內(nèi)聯(lián)到類的定義中,通常,只有那些沒有特別意義的或者性能要求高的,并且是比較短小的函數(shù)才被定義為內(nèi)聯(lián)函數(shù)。更多細(xì)節(jié)參考譯文第一篇的內(nèi)聯(lián)函數(shù)。 12. 編寫短小函數(shù)(Write Short Functions)傾向于選擇短小、凝練的函數(shù)。 長(zhǎng)函數(shù)有時(shí)是恰當(dāng)?shù)模虼藢?duì)于函數(shù)長(zhǎng)度并沒有嚴(yán)格限制。如果函數(shù)超過40行,可以考慮在不影響程序結(jié)構(gòu)的情況下將其分割一下。 即使一個(gè)長(zhǎng)函數(shù)現(xiàn)在工作的非常好,一旦有人對(duì)其修改,有可能出現(xiàn)新的問題,甚至導(dǎo)致難以發(fā)現(xiàn)的bugs。使函數(shù)盡量短小、簡(jiǎn)單,便于他人閱讀和修改代碼。 在處理代碼時(shí),你可能會(huì)發(fā)現(xiàn)復(fù)雜的長(zhǎng)函數(shù),不要害怕修改現(xiàn)有代碼:如果證實(shí)這些代碼使用、調(diào)試?yán)щy,或者你需要使用其中的一小塊,考慮將其分割為更加短小、易于管理的若干函數(shù)。 ______________________________________ 譯者:關(guān)于類的注意事項(xiàng),總結(jié)一下: 1. 不在構(gòu)造函數(shù)中做太多邏輯相關(guān)的初始化; 2. 編譯器提供的默認(rèn)構(gòu)造函數(shù)不會(huì)對(duì)變量進(jìn)行初始化,如果定義了其他構(gòu)造函數(shù),編譯器不再提供,需要編碼者自行提供默認(rèn)構(gòu)造函數(shù); 3. 為避免隱式轉(zhuǎn)換,需將單參數(shù)構(gòu)造函數(shù)聲明為explicit; 4. 為避免拷貝構(gòu)造函數(shù)、賦值操作的濫用和編譯器自動(dòng)生成,可目前聲明其為private且無需實(shí)現(xiàn); 5. 僅在作為數(shù)據(jù)集合時(shí)使用struct; 6. 組合>實(shí)現(xiàn)繼承>接口繼承>私有繼承,子類重載的虛函數(shù)也要聲明virtual關(guān)鍵字,雖然編譯器允許不這樣做; 7. 避免使用多重繼承,使用時(shí),除一個(gè)基類含有實(shí)現(xiàn)外,其他基類均為純接口; 8. 接口類類名以Interface為后綴,除提供帶實(shí)現(xiàn)的虛析構(gòu)函數(shù)、靜態(tài)成員函數(shù)外,其他均為純虛函數(shù),不定義非靜態(tài)數(shù)據(jù)成員,不提供構(gòu)造函數(shù),提供的話,聲明為protected; 9. 為降低復(fù)雜性,盡量不重載操作符,模板、標(biāo)準(zhǔn)類中使用時(shí)提供文檔說明; 10. 存取函數(shù)一般內(nèi)聯(lián)在頭文件中; 11. 聲明次序:public->protected->private; 12. 函數(shù)體盡量短小、緊湊,功能單一。 公司 C++編程風(fēng)格指南(四)公司特有的風(fēng)情公司有很多自己實(shí)現(xiàn)的使C++代碼更加健壯的技巧、功能,以及有異于別處的C++的使用方式。 1. 智能指針(Smart Pointers)如果確實(shí)需要使用智能指針的話,scoped_ptr完全可以勝任。在非常特殊的情況下,例如對(duì)STL容器中對(duì)象,你應(yīng)該只使用std::tr1::shared_ptr,任何情況下都不要使用auto_ptr。 “智能”指針看上去是指針,其實(shí)是附加了語義的對(duì)象。以scoped_ptr為例,scoped_ptr被銷毀時(shí),刪除了它所指向的對(duì)象。shared_ptr也是如此,而且,shared_ptr實(shí)現(xiàn)了引用計(jì)數(shù)(reference-counting),從而只有當(dāng)它所指向的最后一個(gè)對(duì)象被銷毀時(shí),指針才會(huì)被刪除。 一般來說,我們傾向于設(shè)計(jì)對(duì)象隸屬明確的代碼,最明確的對(duì)象隸屬是根本不使用指針,直接將對(duì)象作為一個(gè)域(field)或局部變量使用。另一種極端是引用計(jì)數(shù)指針不屬于任何對(duì)象,這樣設(shè)計(jì)的問題是容易導(dǎo)致循環(huán)引用或其他導(dǎo)致對(duì)象無法刪除的詭異條件,而且在每一次拷貝或賦值時(shí)連原子操作都會(huì)很慢。 雖然不推薦這么做,但有些時(shí)候,引用計(jì)數(shù)指針是最簡(jiǎn)單有效的解決方案。 譯者注:看來,公司所謂的不同之處,在于盡量避免使用智能指針:D,使用時(shí)也盡量局部化,并且,安全第一。 其他C++特性1. 引用參數(shù)(Reference Arguments)所以按引用傳遞的參數(shù)必須加上const。 定義:在C語言中,如果函數(shù)需要修改變量的值,形參(parameter)必須為指針,如int foo(int *pval)。在C++中,函數(shù)還可以聲明引用形參:int foo(int &val)。 優(yōu)點(diǎn):定義形參為引用避免了像(*pval)++這樣丑陋的代碼,像拷貝構(gòu)造函數(shù)這樣的應(yīng)用也是必需的,而且不像指針那樣不接受空指針NULL。 缺點(diǎn):容易引起誤解,因?yàn)橐迷谡Z法上是值卻擁有指針的語義。 結(jié)論: 函數(shù)形參表中,所有引用必須是const: void Foo(conststring &in, string *out); 事實(shí)上這是一個(gè)硬性約定:輸入?yún)?shù)為值或常數(shù)引用,輸出參數(shù)為指針;輸入?yún)?shù)可以是常數(shù)指針,但不能使用非常數(shù)引用形參。 在強(qiáng)調(diào)參數(shù)不是拷貝而來,在對(duì)象生命期內(nèi)必須一直存在時(shí)可以使用常數(shù)指針,最好將這些在注釋中詳細(xì)說明。bind2nd和mem_fun等STL適配器不接受引用形參,這種情況下也必須以指針形參聲明函數(shù)。 2. 函數(shù)重載(Function Overloading)僅在輸入?yún)?shù)類型不同、功能相同時(shí)使用重載函數(shù)(含構(gòu)造函數(shù)),不要使用函數(shù)重載模仿缺省函數(shù)參數(shù)。 定義:可以定義一個(gè)函數(shù)參數(shù)類型為const string&,并定義其重載函數(shù)類型為const char*。 class MyClass { public: void Analyze(const string &text); void Analyze(const char *text, size_t textlen); }; 優(yōu)點(diǎn):通過重載不同參數(shù)的同名函數(shù),令代碼更加直觀,模板化代碼需要重載,同時(shí)為訪問者帶來便利。 缺點(diǎn):限制使用重載的一個(gè)原因是在特定調(diào)用處很難確定到底調(diào)用的是哪個(gè)函數(shù),另一個(gè)原因是當(dāng)派生類只重載函數(shù)的部分變量會(huì)令很多人對(duì)繼承語義產(chǎn)生困惑。此外在閱讀庫的客戶端代碼時(shí),因缺省函數(shù)參數(shù)造成不必要的費(fèi)解。 結(jié)論:如果你想重載一個(gè)函數(shù),考慮讓函數(shù)名包含參數(shù)信息,例如,使用AppendString()、AppendInt()而不是Append()。 3. 缺省參數(shù)(Default Arguments)禁止使用缺省函數(shù)參數(shù)。 優(yōu)點(diǎn):經(jīng)常用到一個(gè)函數(shù)帶有大量缺省值,偶爾會(huì)重寫一下這些值,缺省參數(shù)為很少涉及的例外情況提供了少定義一些函數(shù)的方便。 缺點(diǎn):大家經(jīng)常會(huì)通過查看現(xiàn)有代碼確定如何使用API,缺省參數(shù)使得復(fù)制粘貼以前的代碼難以呈現(xiàn)所有參數(shù),當(dāng)缺省參數(shù)不適用于新代碼時(shí)可能導(dǎo)致重大問題。 結(jié)論:所有參數(shù)必須明確指定,強(qiáng)制程序員考慮API和傳入的各參數(shù)值,避免使用可能不為程序員所知的缺省參數(shù)。 4. 變長(zhǎng)數(shù)組和alloca(Variable-LengthArrays and alloca())禁止使用變長(zhǎng)數(shù)組和alloca()。 優(yōu)點(diǎn):變長(zhǎng)數(shù)組具有渾然天成的語法,變長(zhǎng)數(shù)組和alloca()也都很高效。 缺點(diǎn):變長(zhǎng)數(shù)組和alloca()不是標(biāo)準(zhǔn)C++的組成部分,更重要的是,它們?cè)诙褩#╯tack)上根據(jù)數(shù)據(jù)分配大小可能導(dǎo)致難以發(fā)現(xiàn)的內(nèi)存泄漏:“在我的機(jī)器上運(yùn)行的好好的,到了產(chǎn)品中卻莫名其妙的掛掉了”。 結(jié)論: 使用安全的分配器(allocator),如scoped_ptr/scoped_array。 5. 友元(Friends)允許合理使用友元類及友元函數(shù)。 通常將友元定義在同一文件下,避免讀者跑到其他文件中查找其對(duì)某個(gè)類私有成員的使用。經(jīng)常用到友元的一個(gè)地方是將FooBuilder聲明為Foo的友元,F(xiàn)ooBuilder以便可以正確構(gòu)造Foo的內(nèi)部狀態(tài),而無需將該狀態(tài)暴露出來。某些情況下,將一個(gè)單元測(cè)試用類聲明為待測(cè)類的友元會(huì)很方便。 友元延伸了(但沒有打破)類的封裝界線,當(dāng)你希望只允許另一個(gè)類訪問某個(gè)成員時(shí),使用友元通常比將其聲明為public要好得多。當(dāng)然,大多數(shù)類應(yīng)該只提供公共成員與其交互。 6. 異常(Exceptions)不要使用C++異常。 優(yōu)點(diǎn): 1) 異常允許上層應(yīng)用決定如何處理在底層嵌套函數(shù)中發(fā)生的“不可能發(fā)生”的失敗,不像出錯(cuò)代碼的記錄那么模糊費(fèi)解; 2) 應(yīng)用于其他很多現(xiàn)代語言中,引入異常使得C++與Python、Java及其他與C++相近的語言更加兼容; 3) 許多C++第三方庫使用異常,關(guān)閉異常將導(dǎo)致難以與之結(jié)合; 4) 異常是解決構(gòu)造函數(shù)失敗的唯一方案,雖然可以通過工廠函數(shù)(factoryfunction)或Init()方法模擬異常,但他們分別需要堆分配或新的“非法”狀態(tài); 5) 在測(cè)試框架(testing framework)中,異常確實(shí)很好用。 缺點(diǎn): 1) 在現(xiàn)有函數(shù)中添加throw語句時(shí),必須檢查所有調(diào)用處,即使它們至少具有基本的異常安全保護(hù),或者程序正常結(jié)束,永遠(yuǎn)不可能捕獲該異常。例如:if f() calls g() calls h(),h拋出被f捕獲的異常,g就要當(dāng)心了,避免沒有完全清理; 2) 通俗一點(diǎn)說,異常會(huì)導(dǎo)致程序控制流(control flow)通過查看代碼無法確定:函數(shù)有可能在不確定的地方返回,從而導(dǎo)致代碼管理和調(diào)試?yán)щy,當(dāng)然,你可以通過規(guī)定何時(shí)何地如何使用異常來最小化的降低開銷,卻給開發(fā)人員帶來掌握這些規(guī)定的負(fù)擔(dān); 3) 異常安全需要RAII和不同編碼實(shí)踐。輕松、正確編寫異常安全代碼需要大量支撐。允許使用異常; 4) 加入異常使二進(jìn)制執(zhí)行代碼體積變大,增加了編譯時(shí)長(zhǎng)(或許影響不大),還可能增加地址空間壓力; 5) 異常的實(shí)用性可能會(huì)刺激開發(fā)人員在不恰當(dāng)?shù)臅r(shí)候拋出異常,或者在不安全的地方從異常中恢復(fù),例如,非法用戶輸入可能導(dǎo)致拋出異常。如果允許使用異常會(huì)使得這樣一篇編程風(fēng)格指南長(zhǎng)出很多(譯者注,這個(gè)理由有點(diǎn)牽強(qiáng):-()! 結(jié)論: 從表面上看,使用異常利大于弊,尤其是在新項(xiàng)目中,然而,對(duì)于現(xiàn)有代碼,引入異常會(huì)牽連到所有依賴代碼。如果允許異常在新項(xiàng)目中使用,在跟以前沒有使用異常的代碼整合時(shí)也是一個(gè)麻煩。因?yàn)楣粳F(xiàn)有的大多數(shù)C++代碼都沒有異常處理,引入帶有異常處理的新代碼相當(dāng)困難。 鑒于公司現(xiàn)有代碼不接受異常,在現(xiàn)有代碼中使用異常比在新項(xiàng)目中使用的代價(jià)多少要大一點(diǎn),遷移過程會(huì)比較慢,也容易出錯(cuò)。我們也不相信異常的有效替代方案,如錯(cuò)誤代碼、斷言等,都是嚴(yán)重負(fù)擔(dān)。 我們并不是基于哲學(xué)或道德層面反對(duì)使用異常,而是在實(shí)踐的基礎(chǔ)上。因?yàn)槲覀兿M褂霉旧系拈_源項(xiàng)目,但項(xiàng)目中使用異常會(huì)為此帶來不便,因?yàn)槲覀円步ㄗh不要在公司上的開源項(xiàng)目中使用異常,如果我們需要把這些項(xiàng)目推倒重來顯然不太現(xiàn)實(shí)。 對(duì)于Windows代碼來說,這一點(diǎn)有個(gè)例外(等到最后一篇吧:D)。 譯者注:對(duì)于異常處理,顯然不是短短幾句話能夠說清楚的,以構(gòu)造函數(shù)為例,很多C++書籍上都提到當(dāng)構(gòu)造失敗時(shí)只有異?梢蕴幚,公司禁止使用異常這一點(diǎn),僅僅是為了自身的方便,說大了,無非是基于軟件管理成本上,實(shí)際使用中還是自己決定。 7. 運(yùn)行時(shí)類型識(shí)別(Run-Time Type Information, RTTI)我們禁止使用RTTI。 定義:RTTI允許程序員在運(yùn)行時(shí)識(shí)別C++類對(duì)象的類型。 優(yōu)點(diǎn): RTTI在某些單元測(cè)試中非常有用,如在進(jìn)行工廠類測(cè)試時(shí)用于檢驗(yàn)一個(gè)新建對(duì)象是否為期望的動(dòng)態(tài)類型。 除測(cè)試外,極少用到。 缺點(diǎn):運(yùn)行時(shí)識(shí)別類型意味著設(shè)計(jì)本身有問題,如果你需要在運(yùn)行期間確定一個(gè)對(duì)象的類型,這通常說明你需要重新考慮你的類的設(shè)計(jì)。 結(jié)論: 除單元測(cè)試外,不要使用RTTI,如果你發(fā)現(xiàn)需要所寫代碼因?qū)ο箢愋筒煌鴦?dòng)作各異的話,考慮換一種方式識(shí)別對(duì)象類型。 虛函數(shù)可以實(shí)現(xiàn)隨子類類型不同而執(zhí)行不同代碼,工作都是交給對(duì)象本身去完成。 如果工作在對(duì)象之外的代碼中完成,考慮雙重分發(fā)方案,如Visitor模式,可以方便的在對(duì)象本身之外確定類的類型。 如果你認(rèn)為上面的方法你掌握不了,可以使用RTTI,但務(wù)必請(qǐng)三思,不要去手工實(shí)現(xiàn)一個(gè)貌似RTTI的方案(RTTI-like workaround),我們反對(duì)使用RTTI,同樣反對(duì)貼上類型標(biāo)簽的貌似類繼承的替代方案(譯者注,使用就使用吧,不使用也不要造輪子:D)。 8. 類型轉(zhuǎn)換(Casting)使用static_cast<>()等C++的類型轉(zhuǎn)換,不要使用int y = (int)x或int y = int(x);。 定義:C++引入了有別于C的不同類型的類型轉(zhuǎn)換操作。 優(yōu)點(diǎn):C語言的類型轉(zhuǎn)換問題在于操作比較含糊:有時(shí)是在做強(qiáng)制轉(zhuǎn)換(如(int)3.5),有時(shí)是在做類型轉(zhuǎn)換(如(int)"hello")。另外,C++的類型轉(zhuǎn)換查找更容易、更醒目。 缺點(diǎn):語法比較惡心(nasty)。 結(jié)論:使用C++風(fēng)格而不要使用C風(fēng)格類型轉(zhuǎn)換。 1) static_cast:和C風(fēng)格轉(zhuǎn)換相似可做值的強(qiáng)制轉(zhuǎn)換,或指針的父類到子類的明確的向上轉(zhuǎn)換; 2) const_cast:移除const屬性; 3) reinterpret_cast:指針類型和整型或其他指針間不安全的相互轉(zhuǎn)換,僅在你對(duì)所做一切了然于心時(shí)使用; 4) dynamic_cast:除測(cè)試外不要使用,除單元測(cè)試外,如果你需要在運(yùn)行時(shí)確定類型信息,說明設(shè)計(jì)有缺陷(參考RTTI)。 9. 流(Streams)只在記錄日志時(shí)使用流。 定義:流是printf()和scanf()的替代。 優(yōu)點(diǎn):有了流,在輸出時(shí)不需要關(guān)心對(duì)象的類型,不用擔(dān)心格式化字符串與參數(shù)列表不匹配(雖然在gcc中使用printf也不存在這個(gè)問題),打開、關(guān)閉對(duì)應(yīng)文件時(shí),流可以自動(dòng)構(gòu)造、析構(gòu)。 缺點(diǎn):流使得pread()等功能函數(shù)很難執(zhí)行,如果不使用printf之類的函數(shù)而是使用流很難對(duì)格式進(jìn)行操作(尤其是常用的格式字符串%.*s),流不支持字符串操作符重新定序(%1s),而這一點(diǎn)對(duì)國際化很有用。 結(jié)論: 不要使用流,除非是日志接口需要,使用printf之類的代替。 使用流還有很多利弊,代碼一致性勝過一切,不要在代碼中使用流。 拓展討論: 對(duì)這一條規(guī)則存在一些爭(zhēng)論,這兒給出深層次原因;貞浳ㄒ恍栽瓌t(Only One Way):我們希望在任何時(shí)候都只使用一種確定的I/O類型,使代碼在所有I/O處保持一致。因此,我們不希望用戶來決定是使用流還是printf + read/write,我們應(yīng)該決定到底用哪一種方式。把日志作為例外是因?yàn)榱鞣浅_m合這么做,也有一定的歷史原因。 流的支持者們主張流是不二之選,但觀點(diǎn)并不是那么清晰有力,他們所指出流的所有優(yōu)勢(shì)也正是其劣勢(shì)所在。流最大的優(yōu)勢(shì)是在輸出時(shí)不需要關(guān)心輸出對(duì)象的類型,這是一個(gè)亮點(diǎn),也是一個(gè)不足:很容易用錯(cuò)類型,而編譯器不會(huì)報(bào)警。使用流時(shí)容易造成的一類錯(cuò)誤是: cout << this; // Prints the address cout << *this; // Prints the contents 編譯器不會(huì)報(bào)錯(cuò),因?yàn)?lt;<被重載,就因?yàn)檫@一點(diǎn)我們反對(duì)使用操作符重載。 有人說printf的格式化丑陋不堪、易讀性差,但流也好不到哪兒去?纯聪旅鎯啥未a吧,哪個(gè)更加易讀? cerr << "Error connecting to '" <<foo->bar()->hostname.first <<":" << foo->bar()->hostname.second << ":" << strerror(errno);
fprintf(stderr, "Error connecting to '%s:%u:%s", foo->bar()->hostname.first, foo->bar()->hostname.second, strerror(errno)); 你可能會(huì)說,“把流封裝一下就會(huì)比較好了”,這兒可以,其他地方呢?而且不要忘了,我們的目標(biāo)是使語言盡可能小,而不是添加一些別人需要學(xué)習(xí)的新的內(nèi)容。 每一種方式都是各有利弊,“沒有最好,只有更好”,簡(jiǎn)單化的教條告誡我們必須從中選擇其一,最后的多數(shù)決定是printf + read/write。 10. 前置自增和自減(Preincrement and Predecrement)對(duì)于迭代器和其他模板對(duì)象使用前綴形式(++i)的自增、自減運(yùn)算符。 定義:對(duì)于變量在自增(++i或i++)或自減(--i或i--)后表達(dá)式的值又沒有沒用到的情況下,需要確定到底是使用前置還是后置的自增自減。 優(yōu)點(diǎn):不考慮返回值的話,前置自增(++i)通常要比后置自增(i++)效率更高,因?yàn)楹笾玫淖栽鲎詼p需要對(duì)表達(dá)式的值i進(jìn)行一次拷貝,如果i是迭代器或其他非數(shù)值類型,拷貝的代價(jià)是比較大的。既然兩種自增方式動(dòng)作一樣(譯者注,不考慮表達(dá)式的值,相信你知道我在說什么),為什么不直接使用前置自增呢? 缺點(diǎn):C語言中,當(dāng)表達(dá)式的值沒有使用時(shí),傳統(tǒng)的做法是使用后置自增,特別是在for循環(huán)中,有些人覺得后置自增更加易懂,因?yàn)檫@很像自然語言,主語(i)在謂語動(dòng)詞(++)前。 結(jié)論:對(duì)簡(jiǎn)單數(shù)值(非對(duì)象)來說,兩種都無所謂,對(duì)迭代器和模板類型來說,要使用前置自增(自減)。 11. const的使用(Use of const)我們強(qiáng)烈建議你在任何可以使用的情況下都要使用const。 定義:在聲明的變量或參數(shù)前加上關(guān)鍵字const用于指明變量值不可修改(如const int foo),為類中的函數(shù)加上const限定表明該函數(shù)不會(huì)修改類成員變量的狀態(tài)(如class Foo { int Bar(char c) const; };)。 優(yōu)點(diǎn):人們更容易理解變量是如何使用的,編輯器可以更好地進(jìn)行類型檢測(cè)、更好地生成代碼。人們對(duì)編寫正確的代碼更加自信,因?yàn)樗麄冎浪{(diào)用的函數(shù)被限定了能或不能修改變量值。即使是在無鎖的多線程編程中,人們也知道什么樣的函數(shù)是安全的。 缺點(diǎn):如果你向一個(gè)函數(shù)傳入const變量,函數(shù)原型中也必須是const的(否則變量需要const_cast類型轉(zhuǎn)換),在調(diào)用庫函數(shù)時(shí)這尤其是個(gè)麻煩。 結(jié)論:const變量、數(shù)據(jù)成員、函數(shù)和參數(shù)為編譯時(shí)類型檢測(cè)增加了一層保障,更好的盡早發(fā)現(xiàn)錯(cuò)誤。因此,我們強(qiáng)烈建議在任何可以使用的情況下使用const: 1) 如果函數(shù)不會(huì)修改傳入的引用或指針類型的參數(shù),這樣的參數(shù)應(yīng)該為const; 2) 盡可能將函數(shù)聲明為const,訪問函數(shù)應(yīng)該總是const,其他函數(shù)如果不會(huì)修改任何數(shù)據(jù)成員也應(yīng)該是const,不要調(diào)用非const函數(shù),不要返回對(duì)數(shù)據(jù)成員的非const指針或引用; 3) 如果數(shù)據(jù)成員在對(duì)象構(gòu)造之后不再改變,可將其定義為const。 然而,也不要對(duì)const過度使用,像const int * const * const x;就有些過了,即便這樣寫精確描述了x,其實(shí)寫成const int** x就可以了。 關(guān)鍵字mutable可以使用,但是在多線程中是不安全的,使用時(shí)首先要考慮線程安全。 const位置: 有人喜歡int const *foo形式不喜歡const int* foo,他們認(rèn)為前者更加一致因此可讀性更好:遵循了const總位于其描述的對(duì)象(int)之后的原則。但是,一致性原則不適用于此,“不要過度使用”的權(quán)威抵消了一致性使用。將const放在前面才更易讀,因?yàn)樵谧匀徽Z言中形容詞(const)是在名詞(int)之前的。 這是說,我們提倡const在前,并不是要求,但要兼顧代碼的一致性! 12. 整型(Integer Types)C++內(nèi)建整型中,唯一用到的是int,如果程序中需要不同大小的變量,可以使用<stdint.h>中的精確寬度(precise-width)的整型,如int16_t。 定義:C++沒有指定整型的大小,通常人們認(rèn)為short是16位,int是32位,long是32位,long long是64位。 優(yōu)點(diǎn):保持聲明統(tǒng)一。 缺點(diǎn):C++中整型大小因編譯器和體系結(jié)構(gòu)的不同而不同。 結(jié)論: <stdint.h>定義了int16_t、uint32_t、int64_t等整型,在需要確定大小的整型時(shí)可以使用它們代替short、unsigned long long等,在C整型中,只使用int。適當(dāng)情況下,推薦使用標(biāo)準(zhǔn)類型如size_t和ptrdiff_t。 最常使用的是,對(duì)整數(shù)來說,通常不會(huì)用到太大,如循環(huán)計(jì)數(shù)等,可以使用普通的int。你可以認(rèn)為int至少為32位,但不要認(rèn)為它會(huì)多于32位,需要64位整型的話,可以使用int64_t或uint64_t。 對(duì)于大整數(shù),使用int64_t。 不要使用uint32_t等無符號(hào)整型,除非你是在表示一個(gè)位組(bit pattern)而不是一個(gè)數(shù)值。即使數(shù)值不會(huì)為負(fù)值也不要使用無符號(hào)類型,使用斷言(assertion,譯者注,這一點(diǎn)很有道理,計(jì)算機(jī)只會(huì)根據(jù)變量、返回值等有無符號(hào)確定數(shù)值正負(fù),仍然無法確定對(duì)錯(cuò))來保護(hù)數(shù)據(jù)。 無符號(hào)整型: 有些人,包括一些教科書作者,推薦使用無符號(hào)類型表示非負(fù)數(shù),類型表明了數(shù)值取值形式。但是,在C語言中,這一優(yōu)點(diǎn)被由其導(dǎo)致的bugs所淹沒?纯矗 for (unsigned int i = foo.Length()-1; i >= 0; --i) ... 上述代碼永遠(yuǎn)不會(huì)終止!有時(shí)gcc會(huì)發(fā)現(xiàn)該bug并報(bào)警,但通常不會(huì)。類似的bug還會(huì)出現(xiàn)在比較有符合變量和無符號(hào)變量時(shí),主要是C的類型提升機(jī)制(type-promotion scheme,C語言中各種內(nèi)建類型之間的提升轉(zhuǎn)換關(guān)系)會(huì)致使無符號(hào)類型的行為出乎你的意料。 因此,使用斷言聲明變量為非負(fù)數(shù),不要使用無符號(hào)型。 13. 64位下的可移植性(64-bit Portability)代碼在64位和32位的系統(tǒng)中,原則上應(yīng)該都比較友好,尤其對(duì)于輸出、比較、結(jié)構(gòu)對(duì)齊(structure alignment)來說: 1) printf()指定的一些類型在32位和64位系統(tǒng)上可移植性不是很好,C99標(biāo)準(zhǔn)定義了一些可移植的格式。不幸的是,MSVC 7.1并非全部支持,而且標(biāo)準(zhǔn)中也有所遺漏。所以有時(shí)我們就不得不自己定義丑陋的版本(使用標(biāo)準(zhǔn)風(fēng)格要包含文件inttypes.h): // printf macros for size_t, in the style of inttypes.h #ifdef _LP64 #define __PRIS_PREFIX "z" #else #define __PRIS_PREFIX #endif
// Use these macros after a % in a printf format string // to get correct 32/64 bit behavior, like this: // size_t size = records.size(); // printf("%"PRIuS"\n", size);
#define PRIdS __PRIS_PREFIX "d" #define PRIxS __PRIS_PREFIX "x" #define PRIuS __PRIS_PREFIX "u" #define PRIXS __PRIS_PREFIX "X" #define PRIoS __PRIS_PREFIX "o" 類型 | 不要使用 | 使用 | 備注 | void *(或其他指針類型) | %lx | %p |
| int64_t | %qd, %lld | %"PRId64" |
| uint64_t | %qu, %llu, %llx | %"PRIu64", %"PRIx64" |
| size_t | %u | %"PRIuS", %"PRIxS" | C99指定%zu | ptrdiff_t | %d | %"PRIdS" | C99指定%zd |
注意宏P(guān)RI*會(huì)被編譯器擴(kuò)展為獨(dú)立字符串,因此如果使用非常量的格式化字符串,需要將宏的值而不是宏名插入格式中,在使用宏P(guān)RI*時(shí)同樣可以在%后指定長(zhǎng)度等信息。例如,printf("x = %30"PRIuS"\n", x)在32位Linux上將被擴(kuò)展為printf("x = %30" "u" "\n",x),編譯器會(huì)處理為printf("x = %30u\n", x)。 2) 記住sizeof(void *) != sizeof(int),如果需要一個(gè)指針大小的整數(shù)要使用intptr_t。 3) 需要對(duì)結(jié)構(gòu)對(duì)齊加以留心,尤其是對(duì)于存儲(chǔ)在磁盤上的結(jié)構(gòu)體。在64位系統(tǒng)中,任何擁有int64_t/uint64_t成員的類/結(jié)構(gòu)體將默認(rèn)被處理為8字節(jié)對(duì)齊。如果32位和64位代碼共用磁盤上的結(jié)構(gòu)體,需要確保兩種體系結(jié)構(gòu)下的結(jié)構(gòu)體的對(duì)齊一致。大多數(shù)編譯器提供了調(diào)整結(jié)構(gòu)體對(duì)齊的方案。gcc中可使用__attribute__((packed)),MSVC提供了#pragma pack()和__declspec(align())(譯者注,解決方案的項(xiàng)目屬性里也可以直接設(shè)置)。 4) 創(chuàng)建64位常量時(shí)使用LL或ULL作為后綴,如: int64_t my_value = 0x123456789LL; uint64_t my_mask = 3ULL << 48; 5) 如果你確實(shí)需要32位和64位系統(tǒng)具有不同代碼,可以在代碼變量前使用。(盡量不要這么做,使用時(shí)盡量使修改局部化)。 14. 預(yù)處理宏(Preprocessor Macros)使用宏時(shí)要謹(jǐn)慎,盡量以內(nèi)聯(lián)函數(shù)、枚舉和常量代替之。 宏意味著你和編譯器看到的代碼是不同的,因此可能導(dǎo)致異常行為,尤其是當(dāng)宏存在于全局作用域中。 值得慶幸的是,C++中,宏不像C中那么必要。宏內(nèi)聯(lián)效率關(guān)鍵代碼(performance-critical code)可以內(nèi)聯(lián)函數(shù)替代;宏存儲(chǔ)常量可以const變量替代;宏“縮寫”長(zhǎng)變量名可以引用替代;使用宏進(jìn)行條件編譯,這個(gè)……,最好不要這么做,會(huì)令測(cè)試更加痛苦(#define防止頭文件重包含當(dāng)然是個(gè)例外)。 宏可以做一些其他技術(shù)無法實(shí)現(xiàn)的事情,在一些代碼庫(尤其是底層庫中)可以看到宏的某些特性(如字符串化(stringifying,譯者注,使用#)、連接(concatenation,譯者注,使用##)等等)。但在使用前,仔細(xì)考慮一下能不能不使用宏實(shí)現(xiàn)同樣效果。 譯者注:關(guān)于宏的高級(jí)應(yīng)用,可以參考C語言宏的高級(jí)應(yīng)用。 下面給出的用法模式可以避免一些使用宏的問題,供使用宏時(shí)參考: 1) 不要在.h文件中定義宏; 2) 使用前正確#define,使用后正確#undef; 3) 不要只是對(duì)已經(jīng)存在的宏使用#undef,選擇一個(gè)不會(huì)沖突的名稱; 4) 不使用會(huì)導(dǎo)致不穩(wěn)定的C++構(gòu)造(unbalanced C++ constructs,譯者注)的宏,至少文檔說明其行為。 15. 0和NULL(0 and NULL)整數(shù)用0,實(shí)數(shù)用0.0,指針用NULL,字符(串)用'\0'。 整數(shù)用0,實(shí)數(shù)用0.0,這一點(diǎn)是毫無爭(zhēng)議的。 對(duì)于指針(地址值),到底是用0還是NULL,Bjarne Stroustrup建議使用最原始的0,我們建議使用看上去像是指針的NULL,事實(shí)上一些C++編譯器(如gcc 4.1.0)專門提供了NULL的定義,可以給出有用的警告,尤其是sizeof(NULL)和sizeof(0)不相等的情況。 字符(串)用'\0',不僅類型正確而且可讀性好。 16. sizeof(sizeof)盡可能用sizeof(varname)代替sizeof(type)。 使用sizeof(varname)是因?yàn)楫?dāng)變量類型改變時(shí)代碼自動(dòng)同步,有些情況下sizeof(type)或許有意義,還是要盡量避免,如果變量類型改變的話不能同步。 Struct data; memset(&data, 0, sizeof(data)); memset(&data, 0, sizeof(Struct)); 17. Boost庫(Boost)只使用Boost中被認(rèn)可的庫。 定義:Boost庫集是一個(gè)非常受歡迎的、同級(jí)評(píng)議的(peer-reviewed)、免費(fèi)的、開源的C++庫。 優(yōu)點(diǎn):Boost代碼質(zhì)量普遍較高、可移植性好,填補(bǔ)了C++標(biāo)準(zhǔn)庫很多空白,如型別特性(type traits)、更完善的綁定(binders)、更好的智能指針,同時(shí)還提供了TR1(標(biāo)準(zhǔn)庫的擴(kuò)展)的實(shí)現(xiàn)。 缺點(diǎn):某些Boost庫提倡的編程實(shí)踐可讀性差,像元程序(metaprogramming)和其他高級(jí)模板技術(shù),以及過度“函數(shù)化”("functional")的編程風(fēng)格。 結(jié)論:為了向閱讀和維護(hù)代碼的人員提供更好的可讀性,我們只允許使用Boost特性的一個(gè)成熟子集,當(dāng)前,這些庫包括: 1) Compressed Pair:boost/compressed_pair.hpp; 2) PointerContainer:boost/ptr_container不包括ptr_array.hpp和序列化(serialization)。 我們會(huì)積極考慮添加可以的Boost特性,所以不必拘泥于該規(guī)則。 ______________________________________ 譯者:關(guān)于C++特性的注意事項(xiàng),總結(jié)一下: 1. 對(duì)于智能指針,安全第一、方便第二,盡可能局部化(scoped_ptr); 2. 引用形參加上const,否則使用指針形參; 3. 函數(shù)重載的使用要清晰、易讀; 4. 鑒于容易誤用,禁止使用缺省函數(shù)參數(shù)(值得商榷); 5. 禁止使用變長(zhǎng)數(shù)組; 6. 合理使用友元; 7. 為了方便代碼管理,禁止使用異常(值得商榷); 8. 禁止使用RTTI,否則重新設(shè)計(jì)代碼吧; 9. 使用C++風(fēng)格的類型轉(zhuǎn)換,除單元測(cè)試外不要使用dynamic_cast; 10. 使用流還printf+ read/write,it is a problem; 11. 能用前置自增/減不用后置自增/減; 12. const能用則用,提倡const在前; 13. 使用確定大小的整型,除位組外不要使用無符號(hào)型; 14. 格式化輸出及結(jié)構(gòu)對(duì)齊時(shí),注意32位和64位的系統(tǒng)差異; 15. 除字符串化、連接外盡量避免使用宏; 16. 整數(shù)用0,實(shí)數(shù)用0.0,指針用NULL,字符(串)用'\0'; 17. 用sizeof(varname)代替sizeof(type); 18. 只使用Boost中被認(rèn)可的庫。 公司 C++編程風(fēng)格指南(五)命名約定最重要的一致性規(guī)則是命名管理,命名風(fēng)格直接可以直接確定命名實(shí)體是:類型、變量、函數(shù)、常量、宏等等,無需查找實(shí)體聲明,我們大腦中的模式匹配引擎依賴于這些命名規(guī)則。 命名規(guī)則具有一定隨意性,但相比按個(gè)人喜好命名,一致性更重要,所以不管你怎么想,規(guī)則總歸是規(guī)則。 1. 通用命名規(guī)則(General Naming Rules)函數(shù)命名、變量命名、文件命名應(yīng)具有描述性,不要過度縮寫,類型和變量應(yīng)該是名詞,函數(shù)名可以用“命令性”動(dòng)詞。 如何命名: 盡可能給出描述性名稱,不要節(jié)約空間,讓別人很快理解你的代碼更重要,好的命名選擇: int num_errors; // Good. int num_completed_connections; // Good. 丑陋的命名使用模糊的縮寫或隨意的字符: int n; // Bad - meaningless. int nerr; // Bad - ambiguousabbreviation. int n_comp_conns; // Bad - ambiguousabbreviation. 類型和變量名一般為名詞:如FileOpener、num_errors。 函數(shù)名通常是指令性的,如OpenFile()、set_num_errors(),訪問函數(shù)需要描述的更細(xì)致,要與其訪問的變量相吻合。 縮寫: 除非放到項(xiàng)目外也非常明了,否則不要使用縮寫,例如: // Good // These show proper names with no abbreviations. int num_dns_connections; // Most people know what "DNS" stands for. int price_count_reader; // OK, price count. Makes sense.
// Bad! // Abbreviations can be confusing or ambiguous outside asmall group. int wgc_connections; // Only your group knows what this stands for. int pc_reader; // Lots of things can be abbreviated "pc". 不要用省略字母的縮寫: int error_count; // Good. int error_cnt; //Bad. 2. 文件命名(File Names)文件名要全部小寫,可以包含下劃線(_)或短線(-),按項(xiàng)目約定來。 可接受的文件命名: my_useful_class.cc my-useful-class.cc myusefulclass.cc C++文件以.cc結(jié)尾,頭文件以.h結(jié)尾。 不要使用已經(jīng)存在于/usr/include下的文件名(譯者注,對(duì)UNIX、Linux等系統(tǒng)而言),如db.h。 通常,盡量讓文件名更加明確,http_server_logs.h就比logs.h要好,定義類時(shí)文件名一般成對(duì)出現(xiàn),如foo_bar.h和foo_bar.cc,對(duì)應(yīng)類FooBar。 內(nèi)聯(lián)函數(shù)必須放在.h文件中,如果內(nèi)聯(lián)函數(shù)比較短,就直接放在.h中。如果代碼比較長(zhǎng),可以放到以-inl.h結(jié)尾的文件中。對(duì)于包含大量?jī)?nèi)聯(lián)代碼的類,可以有三個(gè)文件: url_table.h //The class declaration. url_table.cc //The class definition. url_table-inl.h //Inline functions that include lots of code. 參考第一篇-inl.h文件一節(jié)。 3. 類型命名(Type Names)類型命名每個(gè)單詞以大寫字母開頭,不包含下劃線:MyExcitingClass、MyExcitingEnum。 所有類型命名——類、結(jié)構(gòu)體、類型定義(typedef)、枚舉——使用相同約定,例如: // classes and structs class UrlTable { ... class UrlTableTester { ... struct UrlTableProperties { ...
// typedefs typedef hash_map<UrlTableProperties *, string>PropertiesMap;
// enums enum UrlTableErrors { ... 4. 變量命名(Variable Names)變量名一律小寫,單詞間以下劃線相連,類的成員變量以下劃線結(jié)尾,如my_exciting_local_variable、my_exciting_member_variable_。 普通變量命名: 舉例: string table_name; // OK - uses underscore. string tablename; // OK - all lowercase. string tableName; // Bad - mixed case. 類數(shù)據(jù)成員: 結(jié)構(gòu)體的數(shù)據(jù)成員可以和普通變量一樣,不用像類那樣接下劃線: struct UrlTableProperties { string name; int num_entries; } 結(jié)構(gòu)體與類的討論參考第三篇結(jié)構(gòu)體vs.類一節(jié)。 全局變量: 對(duì)全局變量沒有特別要求,少用就好,可以以g_或其他易與局部變量區(qū)分的標(biāo)志為前綴。 5. 常量命名(Constant Names)在名稱前加k:kDaysInAWeek。 所有編譯時(shí)常量(無論是局部的、全局的還是類中的)和其他變量保持些許區(qū)別,k后接大寫字母開頭的單詞: const int kDaysInAWeek = 7; 6. 函數(shù)命名(Function Names)普通函數(shù)(regular functions,譯者注,這里與訪問函數(shù)等特殊函數(shù)相對(duì))大小寫混合,存取函數(shù)(accessors andmutators)則要求與變量名匹配:MyExcitingFunction()、MyExcitingMethod()、my_exciting_member_variable()、set_my_exciting_member_variable()。 普通函數(shù): 函數(shù)名以大寫字母開頭,每個(gè)單詞首字母大寫,沒有下劃線: AddTableEntry() DeleteUrl() 存取函數(shù): 存取函數(shù)要與存取的變量名匹配,這兒摘錄一個(gè)擁有實(shí)例變量num_entries_的類: class MyClass { public: ... int num_entries()const { return num_entries_; } voidset_num_entries(int num_entries) { num_entries_ = num_entries; }
private: int num_entries_; }; 其他短小的內(nèi)聯(lián)函數(shù)名也可以使用小寫字母,例如,在循環(huán)中調(diào)用這樣的函數(shù)甚至都不需要緩存其值,小寫命名就可以接受。 譯者注:從這一點(diǎn)上可以看出,小寫的函數(shù)名意味著可以直接內(nèi)聯(lián)使用。 7. 命名空間(Namespace Names)命名空間的名稱是全小寫的,其命名基于項(xiàng)目名稱和目錄結(jié)構(gòu):公司_awesome_project。 關(guān)于命名空間的討論和如何命名,參考第二篇命名空間。 8. 枚舉命名(Enumerator Names)枚舉值應(yīng)全部大寫,單詞間以下劃線相連:MY_EXCITING_ENUM_VALUE。 枚舉名稱屬于類型,因此大小寫混合:UrlTableErrors。 enum UrlTableErrors { OK = 0, ERROR_OUT_OF_MEMORY, ERROR_MALFORMED_INPUT, }; 9. 宏命名(Macro Names)你并不打算使用宏,對(duì)吧?如果使用,像這樣:MY_MACRO_THAT_SCARES_SMALL_CHILDREN。 參考第四篇預(yù)處理宏,通常是不使用宏的,如果絕對(duì)要用,其命名像枚舉命名一樣全部大寫、使用下劃線: #define ROUND(x) ... #define PI_ROUNDED 3.0 MY_EXCITING_ENUM_VALUE 10. 命名規(guī)則例外(Exceptions to Naming Rules)當(dāng)命名與現(xiàn)有C/C++實(shí)體相似的對(duì)象時(shí),可參考現(xiàn)有命名約定: bigopen() 函數(shù)名,參考o(jì)pen() uint typedef類型定義 bigpos struct或class,參考pos sparse_hash_map STL相似實(shí)體;參考STL命名約定 LONGLONG_MAX 常量,類似INT_MAX ______________________________________ 譯者:命名約定就相對(duì)輕松許多,在遵從代碼一致性、可讀性的前提下,略顯隨意: 1. 總體規(guī)則:不要隨意縮寫,如果說ChangeLocalValue寫作ChgLocVal還有情可原的話,把ModifyPlayerName寫作MdfPlyNm就太過分了,除函數(shù)名可適當(dāng)為動(dòng)詞外,其他命名盡量使用清晰易懂的名詞; 2. 宏、枚舉等使用全部大寫+下劃線; 3. 變量(含類、結(jié)構(gòu)體成員變量)、文件、命名空間、存取函數(shù)等使用全部小寫+下劃線,類成員變量以下劃線結(jié)尾,全局變量以g_開頭; 4. 普通函數(shù)、類型(含類與結(jié)構(gòu)體、枚舉類型)、常量等使用大小寫混合,不含下劃線; 5. 參考現(xiàn)有或相近命名約定。 公司 C++編程風(fēng)格指南(六)注釋注釋雖然寫起來很痛苦,但對(duì)保證代碼可讀性至為重要,下面的規(guī)則描述了應(yīng)該注釋什么、注釋在哪兒。當(dāng)然也要記住,注釋的確很重要,但最好的代碼本身就是文檔(self-documenting),類型和變量命名意義明確要比通過注釋解釋模糊的命名好得多。 注釋是為別人(下一個(gè)需要理解你的代碼的人)而寫的,認(rèn)真點(diǎn)吧,那下一個(gè)人可能就是你! 1. 注釋風(fēng)格(Comment Style)使用//或/* */,統(tǒng)一就好。 //或/* */都可以,//只是用的更加廣泛,在如何注釋和注釋風(fēng)格上確保統(tǒng)一。 2. 文件注釋(File Comments)在每一個(gè)文件開頭加入版權(quán)公告,然后是文件內(nèi)容描述。 法律公告和作者信息: 每一文件包含以下項(xiàng),依次是: 1) 版權(quán)(copyright statement):如Copyright 2008 公司 Inc.; 2) 許可版本(license boilerplate):為項(xiàng)目選擇合適的許可證版本,如Apache 2.0、BSD、LGPL、GPL; 3) 作者(author line):標(biāo)識(shí)文件的原始作者。 如果你對(duì)其他人創(chuàng)建的文件做了重大修改,將你的信息添加到作者信息里,這樣當(dāng)其他人對(duì)該文件有疑問時(shí)可以知道該聯(lián)系誰。 文件內(nèi)容: 每一個(gè)文件版權(quán)許可及作者信息后,都要對(duì)文件內(nèi)容進(jìn)行注釋說明。 通常,.h文件要對(duì)所聲明的類的功能和用法作簡(jiǎn)單說明,.cc文件包含了更多的實(shí)現(xiàn)細(xì)節(jié)或算法討論,如果你感覺這些實(shí)現(xiàn)細(xì)節(jié)或算法討論對(duì)于閱讀有幫助,可以把.cc中的注釋放到.h中,并在.cc中指出文檔在.h中。 不要單純?cè)?h和.cc間復(fù)制注釋,復(fù)制的注釋偏離了實(shí)際意義。 3. 類注釋(Class Comments)每個(gè)類的定義要附著描述類的功能和用法的注釋。 // Iterates over the contents of a GargantuanTable. Sample usage: // GargantuanTable_Iterator* iter = table->NewIterator(); // for(iter->Seek("foo"); !iter->done(); iter->Next()) { // process(iter->key(), iter->value()); // } // delete iter; class GargantuanTable_Iterator { ... };
如果你覺得已經(jīng)在文件頂部詳細(xì)描述了該類,想直接簡(jiǎn)單的來上一句“完整描述見文件頂部”的話,還是多少在類中加點(diǎn)注釋吧。 如果類有任何同步前提(synchronizationassumptions),文檔說明之。如果該類的實(shí)例可被多線程訪問,使用時(shí)務(wù)必注意文檔說明。 4. 函數(shù)注釋(Function Comments)函數(shù)聲明處注釋描述函數(shù)功能,定義處描述函數(shù)實(shí)現(xiàn)。 函數(shù)聲明: 注釋于聲明之前,描述函數(shù)功能及用法,注釋使用描述式("Opens the file")而非指令式("Open the file");注釋只是為了描述函數(shù)而不是告訴函數(shù)做什么。通常,注釋不會(huì)描述函數(shù)如何實(shí)現(xiàn),那是定義部分的事情。 函數(shù)聲明處注釋的內(nèi)容: 1) inputs(輸入)及outputs(輸出); 2) 對(duì)類成員函數(shù)而言:函數(shù)調(diào)用期間對(duì)象是否需要保持引用參數(shù),是否會(huì)釋放這些參數(shù); 3) 如果函數(shù)分配了空間,需要由調(diào)用者釋放; 4) 參數(shù)是否可以為NULL; 5) 是否存在函數(shù)使用的性能隱憂(performance implications); 6) 如果函數(shù)是可重入的(re-entrant),其同步前提(synchronization assumptions)是什么? 舉例如下: // Returns an iterator for this table. It is the client's // responsibility to delete the iterator when it is donewith it, // and it must not use the iterator once theGargantuanTable object // on which the iterator was created has been deleted. // // The iterator is initially positioned at the beginningof the table. // // This method is equivalent to: // Iterator*iter = table->NewIterator(); // iter->Seek(""); // return iter; // If you are going to immediately seek to another placein the // returned iterator, it will be faster to useNewIterator() // and avoid the extra seek. Iterator* GetIterator() const;
但不要有無謂冗余或顯而易見的注釋,下面的注釋就沒有必要加上“returns false otherwise”,因?yàn)橐呀?jīng)暗含其中了: // Returns true if the table cannot hold any moreentries. bool IsTableFull();
注釋構(gòu)造/析構(gòu)函數(shù)時(shí),記住,讀代碼的人知道構(gòu)造/析構(gòu)函數(shù)是什么,所以“destroysthis object”這樣的注釋是沒有意義的。說明構(gòu)造函數(shù)對(duì)參數(shù)做了什么(例如,是否是指針的所有者)以及析構(gòu)函數(shù)清理了什么,如果都是無關(guān)緊要的內(nèi)容,直接省掉注釋,析構(gòu)函數(shù)前沒有注釋是很正常的。 函數(shù)定義: 每個(gè)函數(shù)定義時(shí)要以注釋說明函數(shù)功能和實(shí)現(xiàn)要點(diǎn),如使用的漂亮代碼、實(shí)現(xiàn)的簡(jiǎn)要步驟、如此實(shí)現(xiàn)的理由、為什么前半部分要加鎖而后半部分不需要。 不要從.h文件或其他地方的函數(shù)聲明處直接復(fù)制注釋,簡(jiǎn)要說明函數(shù)功能是可以的,但重點(diǎn)要放在如何實(shí)現(xiàn)上。 5. 變量注釋(Variable Comments)通常變量名本身足以很好說明變量用途,特定情況下,需要額外注釋說明。 類數(shù)據(jù)成員: 每個(gè)類數(shù)據(jù)成員(也叫實(shí)例變量或成員變量)應(yīng)注釋說明用途,如果變量可以接受NULL或-1等警戒值(sentinel values),須說明之,如: private: // Keeps track ofthe total number of entries in the table. // Used to ensurewe do not go over the limit. -1 means // that we don'tyet know how many entries the table has. intnum_total_entries_;
全局變量(常量): 和數(shù)據(jù)成員相似,所有全局變量(常量)也應(yīng)注釋說明含義及用途,如: // The total number of tests cases that we run through inthis regression test. const int kNumTestCases = 6;
6. 實(shí)現(xiàn)注釋(Implementation Comments)對(duì)于實(shí)現(xiàn)代碼中巧妙的、晦澀的、有趣的、重要的地方加以注釋。 代碼前注釋: 出彩的或復(fù)雜的代碼塊前要加注釋,如: // 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); (*result)[ i] = x>> 1; x &= 1; }
行注釋: 比較隱晦的地方要在行尾加入注釋,可以在代碼之后空兩格加行尾注釋,如: // If we have enough memory, mmap the data portion too. mmap_budget = max<int64>(0, mmap_budget -index_->length()); if (mmap_budget >= data_size_ &&!MmapData(mmap_chunk_bytes, mlock)) return; // Error already logged.
注意,有兩塊注釋描述這段代碼,當(dāng)函數(shù)返回時(shí)注釋提及錯(cuò)誤已經(jīng)被記入日志。 前后相鄰幾行都有注釋,可以適當(dāng)調(diào)整使之可讀性更好: ... DoSomething(); // Comment here so thecomments line up. DoSomethingElseThatIsLonger(); // Comment here so there are two spacesbetween // the code andthe comment. ...
NULL、true/false、1、2、3……: 向函數(shù)傳入、布爾值或整數(shù)時(shí),要注釋說明含義,或使用常量讓代碼望文知意,比較一下: bool success = CalculateSomething(interesting_value, 10, false, NULL); // What are these arguments??
和: 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);
不要: 注意永遠(yuǎn)不要用自然語言翻譯代碼作為注釋,要假設(shè)讀你代碼的人C++比你強(qiáng):D: // Now go through the b array and make sure that if ioccurs, // the next element is i+1. ... //Geez. What a useless comment.
7. 標(biāo)點(diǎn)、拼寫和語法(Punctuation, Spelling and Grammar)留意標(biāo)點(diǎn)、拼寫和語法,寫的好的注釋比差的要易讀的多。 注釋一般是包含適當(dāng)大寫和句點(diǎn)(.)的完整的句子,短一點(diǎn)的注釋(如代碼行尾的注釋)可以隨意點(diǎn),依然要注意風(fēng)格的一致性。完整的句子可讀性更好,也可以說明該注釋是完整的而不是一點(diǎn)不成熟的想法。 雖然被別人指出該用分號(hào)(semicolon)的時(shí)候用了逗號(hào)(comma)有點(diǎn)尷尬。清晰易讀的代碼還是很重要的,適當(dāng)?shù)臉?biāo)點(diǎn)、拼寫和語法對(duì)此會(huì)有所幫助。 8. TODO注釋(TODO Comments)對(duì)那些臨時(shí)的、短期的解決方案,或已經(jīng)夠好但并不完美的代碼使用TODO注釋。 這樣的注釋要使用全大寫的字符串TODO,后面括號(hào)(parentheses)里加上你的大名、郵件地址等,還可以加上冒號(hào)(colon):目的是可以根據(jù)統(tǒng)一的TODO格式進(jìn)行查找: // TODO(kl@gmail.com): Use a "*" here forconcatenation operator. // TODO(Zeke) change this to use relations.
如果加上是為了在“將來某一天做某事”,可以加上一個(gè)特定的時(shí)間("Fix by November2005")或事件("Remove this code whenall clients can handle XML responses.")。 ______________________________________ 譯者:注釋也是比較人性化的約定了: 1. 關(guān)于注釋風(fēng)格,很多C++的coders更喜歡行注釋,C coders或許對(duì)塊注釋依然情有獨(dú)鐘,或者在文件頭大段大段的注釋時(shí)使用塊注釋; 2. 文件注釋可以炫耀你的成就,也是為了捅了簍子別人可以找你; 3. 注釋要言簡(jiǎn)意賅,不要拖沓冗余,復(fù)雜的東西簡(jiǎn)單化和簡(jiǎn)單的東西復(fù)雜化都是要被鄙視的; 4. 對(duì)于Chinese coders來說,用英文注釋還是用中文注釋,it is a problem,但不管怎樣,注釋是為了讓別人看懂,難道是為了炫耀編程語言之外的你的母語或外語水平嗎; 5. 注釋不要太亂,適當(dāng)?shù)目s進(jìn)才會(huì)讓人樂意看,但也沒有必要規(guī)定注釋從第幾列開始(我自己寫代碼的時(shí)候總喜歡這樣),UNIX/LINUX下還可以約定是使用tab還是space,個(gè)人傾向于space; 6. TODO很不錯(cuò),有時(shí)候,注釋確實(shí)是為了標(biāo)記一些未完成的或完成的不盡如人意的地方,這樣一搜索,就知道還有哪些活要干,日志都省了。 公司 C++編程風(fēng)格指南(七)格式代碼風(fēng)格和格式確實(shí)比較隨意,但一個(gè)項(xiàng)目中所有人遵循同一風(fēng)格是非常容易的,作為個(gè)人未必同意下述格式規(guī)則的每一處,但整個(gè)項(xiàng)目服從統(tǒng)一的編程風(fēng)格是很重要的,這樣做才能讓所有人在閱讀和理解代碼時(shí)更加容易。 1. 行長(zhǎng)度(Line Length)每一行代碼字符數(shù)不超過80。 我們也認(rèn)識(shí)到這條規(guī)則是存有爭(zhēng)議的,但如此多的代碼都遵照這一規(guī)則,我們感覺一致性更重要。 優(yōu)點(diǎn):提倡該原則的人認(rèn)為強(qiáng)迫他們調(diào)整編輯器窗口大小很野蠻。很多人同時(shí)并排開幾個(gè)窗口,根本沒有多余空間拓寬某個(gè)窗口,人們將窗口最大尺寸加以限定,一致使用80列寬,為什么要改變呢? 缺點(diǎn):反對(duì)該原則的人則認(rèn)為更寬的代碼行更易閱讀,80列的限制是上個(gè)世紀(jì)60年代的大型機(jī)的古板缺陷;現(xiàn)代設(shè)備具有更寬的顯示屏,很輕松的可以顯示更多代碼。 結(jié)論:80個(gè)字符是最大值。例外: 1) 如果一行注釋包含了超過80字符的命令或URL,出于復(fù)制粘貼的方便可以超過80字符; 2) 包含長(zhǎng)路徑的可以超出80列,盡量避免; 3) 頭文件保護(hù)(防止重復(fù)包含第一篇)可以無視該原則。 2. 非ASCII字符(Non-ASCII Characters)盡量不使用非ASCII字符,使用時(shí)必須使用UTF-8格式。 哪怕是英文,也不應(yīng)將用戶界面的文本硬編碼到源代碼中,因此非ASCII字符要少用。特殊情況下可以適當(dāng)包含此類字符,如,代碼分析外部數(shù)據(jù)文件時(shí),可以適當(dāng)硬編碼數(shù)據(jù)文件中作為分隔符的非ASCII字符串;更常用的是(不需要本地化的)單元測(cè)試代碼可能包含非ASCII字符串。此類情況下,應(yīng)使用UTF-8格式,因?yàn)楹芏喙ぞ叨伎梢岳斫夂吞幚砥渚幋a,十六進(jìn)制編碼也可以,尤其是在增強(qiáng)可讀性的情況下——如"\xEF\xBB\xBF"是Unicode的zero-width no-break space字符,以UTF-8格式包含在源文件中是不可見的。 3. 空格還是制表位(Spaces vs. Tabs)只使用空格,每次縮進(jìn)2個(gè)空格。 使用空格進(jìn)行縮進(jìn),不要在代碼中使用tabs,設(shè)定編輯器將tab轉(zhuǎn)為空格。 譯者注:在前段時(shí)間的關(guān)于Debian開發(fā)學(xué)習(xí)日記一文中,曾給出針對(duì)C/C++編碼使用的vim配置。 4. 函數(shù)聲明與定義(Function Declarations and Definitions)返回類型和函數(shù)名在同一行,合適的話,參數(shù)也放在同一行。 函數(shù)看上去像這樣: ReturnType ClassName::FunctionName(Type par_name1, Typepar_name2) { DoSomething(); ... }
如果同一行文本較多,容不下所有參數(shù): ReturnType ClassName::ReallyLongFunctionName(Typepar_name1, Type par_name2, Type par_name3) { DoSomething(); ... }
甚至連第一個(gè)參數(shù)都放不下: ReturnTypeLongClassName::ReallyReallyReallyLongFunctionName( Typepar_name1, // 4 space indent Type par_name2, Type par_name3){ DoSomething(); // 2 space indent ... }
注意以下幾點(diǎn): 1) 返回值總是和函數(shù)名在同一行; 2) 左圓括號(hào)(open parenthesis)總是和函數(shù)名在同一行; 3) 函數(shù)名和左圓括號(hào)間沒有空格; 4) 圓括號(hào)與參數(shù)間沒有空格; 5) 左大括號(hào)(open curly brace)總在最后一個(gè)參數(shù)同一行的末尾處; 6) 右大括號(hào)(close curly brace)總是單獨(dú)位于函數(shù)最后一行; 7) 右圓括號(hào)(close parenthesis)和左大括號(hào)間總是有一個(gè)空格; 8) 函數(shù)聲明和實(shí)現(xiàn)處的所有形參名稱必須保持一致; 9) 所有形參應(yīng)盡可能對(duì)齊; 10) 缺省縮進(jìn)為2個(gè)空格; 11) 獨(dú)立封裝的參數(shù)保持4個(gè)空格的縮進(jìn)。 如果函數(shù)為const的,關(guān)鍵字const應(yīng)與最后一個(gè)參數(shù)位于同一行。 // Everything in this function signature fits on a singleline ReturnType FunctionName(Type par) const { ... }
// This function signature requires multiple lines, but // the const keyword is on the line with the lastparameter. ReturnType ReallyLongFunctionName(Type par1, Type par2)const { ... }
如果有些參數(shù)沒有用到,在函數(shù)定義處將參數(shù)名注釋起來: // Always have named parameters in interfaces. class Shape { public: virtual voidRotate(double radians) = 0; }
// Always have named parameters in the declaration. class Circle : public Shape { public: virtual voidRotate(double radians); }
// Comment out unused named parameters in definitions. void Circle::Rotate(double /*radians*/) {} // Bad - if someone wants to implement later, it's notclear what the // variable means. void Circle::Rotate(double) {}
譯者注:關(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)是匹配情況一目了然。 5. 函數(shù)調(diào)用(Function Calls)盡量放在同一行,否則,將實(shí)參封裝在圓括號(hào)中。 函數(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),以至于超過行最大長(zhǎng)度,可以將所有參數(shù)獨(dú)立成行: if (...) { ... ... if (...) { DoSomethingThatRequiresALongFunctionName( very_long_argument1, // 4 space indent argument2, argument3, argument4); }
6. 條件語句(Conditionals)更提倡不在圓括號(hào)中添加空格,關(guān)鍵字else另起一行。 對(duì)基本條件語句有兩種可以接受的格式,一種在圓括號(hào)和條件之間有空格,一種沒有。 最常見的是沒有空格的格式,那種都可以,還是一致性為主。如果你是在修改一個(gè)文件,參考當(dāng)前已有格式;如果是寫新的代碼,參考目錄下或項(xiàng)目中其他文件的格式,還在徘徊的話,就不要加空格了。 if (condition) { // no spaces inside parentheses ... // 2 space indent. } else { // Theelse goes on the same line as the closing brace. ... }
如果你傾向于在圓括號(hào)內(nèi)部加空格: if ( condition ) { // spaces inside parentheses - rare ... // 2 space indent. } else { // Theelse goes on the same line as the closing brace. ... }
注意所有情況下if和左圓括號(hào)間有個(gè)空格,右圓括號(hào)和左大括號(hào)(如果使用的話)間也要有個(gè)空格: if(condition) // Bad - space missing after IF. if (condition){ // Bad - space missing before {. if(condition){ // Doubly bad. if (condition) { // Good - proper space after IF and before {.
有些條件語句寫在同一行以增強(qiáng)可讀性,只有當(dāng)語句簡(jiǎn)單并且沒有使用else子句時(shí)使用: if (x == kFoo) return new Foo(); if (x == kBar) return new Bar();
如果語句有else分支是不允許的: // Not allowed - IF statement on one line when there isan ELSE clause if (x) DoThis(); else DoThat();
通常,單行語句不需要使用大括號(hào),如果你喜歡也無可厚非,也有人要求if必須使用大括號(hào): if (condition) DoSomething(); // 2 space indent.
if (condition) { DoSomething(); // 2 space indent. }
但如果語句中哪一分支使用了大括號(hào)的話,其他部分也必須使用: // Not allowed - curly on IF but not ELSE if (condition) { foo; } else bar;
// Not allowed - curly on ELSE but not IF if (condition) foo; else { bar; }
// Curly braces around both IF and ELSE required because // one of the clauses used braces. if (condition) { foo; } else { bar; }
7. 循環(huán)和開關(guān)選擇語句(Loops and Switch Statements)switch語句可以使用大括號(hào)分塊;空循環(huán)體應(yīng)使用{}或continue。 switch語句中的case塊可以使用大括號(hào)也可以不用,取決于你的喜好,使用時(shí)要依下文所述。 如果有不滿足case枚舉條件的值,要總是包含一個(gè)default(如果有輸入值沒有case去處理,編譯器將報(bào)警)。如果default永不會(huì)執(zhí)行,可以簡(jiǎn)單的使用assert: 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 testuntil it returns false. } for (int i = 0; i < kSomeNumber; ++i) {} // Good - empty body. while (condition) continue; // Good - continue indicates no logic. while (condition); // Bad - looks like part of do/while loop.
8. 指針和引用表達(dá)式(Pointers and Reference Expressions)句點(diǎn)(.)或箭頭(->)前后不要有空格,指針/地址操作符(*、&)后不要有空格。 下面是指針和引用表達(dá)式的正確范例: x = *p; p = &x; x = r.y; x = r->y;
注意: 1) 在訪問成員時(shí),句點(diǎn)或箭頭前后沒有空格; 2) 指針操作符*或&后沒有空格。 在聲明指針變量或參數(shù)時(shí),星號(hào)與類型或變量名緊挨都可以: // These are fine, space preceding. char *c; const string &str;
// These are fine, space following. char* c; // butremember to do "char* c, *d, *e, ...;"! const string& str; char * c; // Bad -spaces on both sides of * const string & str; // Bad - spaces on both sides of &
同一個(gè)文件(新建或現(xiàn)有)中起碼要保持一致。 譯者注:個(gè)人比較習(xí)慣與變量緊挨的方式。 9. 布爾表達(dá)式(Boolean Expressions)如果一個(gè)布爾表達(dá)式超過標(biāo)準(zhǔn)行寬(80字符),如果斷行要統(tǒng)一一下。 下例中,邏輯與(&&)操作符總位于行尾: if (this_one_thing > this_other_thing && a_third_thing== a_fourth_thing && yet_another& last_one) { ... }
兩個(gè)邏輯與(&&)操作符都位于行尾,可以考慮額外插入圓括號(hào),合理使用的話對(duì)增強(qiáng)可讀性是很有幫助的。 譯者注:個(gè)人比較習(xí)慣邏輯運(yùn)算符位于行首,邏輯關(guān)系一目了然,各人喜好而已,至于加不加圓括號(hào)的問題,如果你對(duì)優(yōu)先級(jí)了然于胸的話可以不加,但可讀性總是差了些。 10. 函數(shù)返回值(Return Values)return表達(dá)式中不要使用圓括號(hào)。 函數(shù)返回時(shí)不要使用圓括號(hào): return x; // notreturn(x);
11. 變量及數(shù)組初始化(Variable and Array Initialization)選擇=還是()。 需要做二者之間做出選擇,下面的形式都是正確的: int x = 3; int x(3); string name("Some Name"); string name = "Some Name";
12. 預(yù)處理指令(Preprocessor Directives)預(yù)處理指令不要縮進(jìn),從行首開始。 即使預(yù)處理指令位于縮進(jìn)代碼塊中,指令也應(yīng)從行首開始。 // Good - directives at beginning of line if(lopsided_score) { #if DISASTER_PENDING // Correct -- Starts at beginning of line DropEverything(); #endif BackToNormal(); } // Bad - indented directives if(lopsided_score) { #ifDISASTER_PENDING // Wrong! The "#if" should be at beginning ofline DropEverything(); #endif // Wrong! Do not indent "#endif" BackToNormal(); }
13. 類格式(Class Format)聲明屬性依次序是public:、protected:、private:,每次縮進(jìn)1個(gè)空格(譯者注,為什么不是兩個(gè)呢?也有人提倡private在前,對(duì)于聲明了哪些數(shù)據(jù)成員一目了然,還有人提倡依邏輯關(guān)系將變量與操作放在一起,都有道理:-))。 類聲明(對(duì)類注釋不了解的話,參考第六篇中的類注釋一節(jié))的基本格式如下: class MyClass : public OtherClass { public: // Note the 1 space indent! MyClass(); // Regular 2 space indent. explicitMyClass(int var); ~MyClass() {}
voidSomeFunction(); void SomeFunctionThatDoesNothing(){ }
voidset_some_var(int var) { some_var_ = var; } int some_var()const { return some_var_; }
private: boolSomeInternalFunction();
int some_var_; intsome_other_var_; DISALLOW_COPY_AND_ASSIGN(MyClass); };
注意: 1) 所以基類名應(yīng)在80列限制下盡量與子類名放在同一行; 2) 關(guān)鍵詞public:、protected:、private:要縮進(jìn)1個(gè)空格(譯者注,MSVC多使用tab縮進(jìn),且這三個(gè)關(guān)鍵詞沒有縮進(jìn)); 3) 除第一個(gè)關(guān)鍵詞(一般是public)外,其他關(guān)鍵詞前空一行,如果類比較小的話也可以不空; 4) 這些關(guān)鍵詞后不要空行; 5) public放在最前面,然后是protected和private; 6) 關(guān)于聲明次序參考第三篇聲明次序一節(jié)。 14. 初始化列表(Initializer Lists)構(gòu)造函數(shù)初始化列表放在同一行或按四格縮進(jìn)并排幾行。 兩種可以接受的初始化列表格式: // When it all fits on one line: MyClass::MyClass(int var) : some_var_(var),some_other_var_(var + 1) {
或 // When it requires multiple lines, indent 4 spaces,putting the colon on // the first initializer line: MyClass::MyClass(int var) :some_var_(var), // 4 spaceindent some_other_var_(var + 1) { //lined up ... DoSomething(); ... }
15. 命名空間格式化(Namespace Formatting)命名空間內(nèi)容不縮進(jìn)。 命名空間不添加額外縮進(jìn)層次,例如: namespace {
void foo() { //Correct. No extra indentation withinnamespace. ... }
} // namespace
不要縮進(jìn): namespace {
// Wrong. Indented when it should not be. void foo() { ... }
} // namespace
16. 水平留白(Horizontal Whitespace)水平留白的使用因地制宜。不要在行尾添加無謂的留白。 普通: void f(bool b) { // Open braces should always have a space before them. ... int i = 0; //Semicolons usually have no space before them. int x[] = { 0 }; // Spaces inside braces for array initialization are int x[] = {0}; // optional. If you use them, putthem on both sides! // Spaces around the colon in inheritance and initializerlists. class Foo : public Bar { public: // For inlinefunction implementations, put spaces between the braces // and theimplementation itself. Foo(int b) :Bar(), baz_(b) {} // No spaces insideempty braces. void Reset() {baz_ = 0; } // Spaces separating bracesfrom implementation. ...
添加冗余的留白會(huì)給其他人編輯時(shí)造成額外負(fù)擔(dān),因此,不要加入多余的空格。如果確定一行代碼已經(jīng)修改完畢,將多余的空格去掉;或者在專門清理空格時(shí)去掉(確信沒有其他人在使用)。 循環(huán)和條件語句: if (b) { // Space after the keyword in conditions and loops. } else { // Spaces around else. } while (test) {} // There is usually no space inside parentheses. switch (i) { for (int i = 0; i < 5; ++i) { switch ( i ) { // Loops and conditions may have spaces inside if ( test ) { // parentheses, but this is rare. Be consistent. for ( int i = 0; i < 5; ++i ) { for ( ; i < 5 ; ++i) { // For loops always have a space after the ... // semicolon, and may have aspace before the // semicolon. switch (i) { case 1: // No space before colon in a switchcase. ... case 2:break; // Use a space after a colon if there'scode after it.
操作符: x = 0; // Assignment operators always have spaces around // them. x = -5; // No spaces separating unary operators and their ++x; // arguments. if (x && !y) ... v = w * x + y / z; // Binary operators usually have spaces around them, v = w*x + y/z; // but it's okay to remove spaces around factors. v = w * (x + z); // Parentheses should have no spaces inside them.
模板和轉(zhuǎn)換: vector<string> x; // No spaces inside the angle y = static_cast<char*>(x); // brackets (< and >), before // <, or between>( in a cast. vector<char *> x; // Spaces between type and pointerare // okay, but beconsistent. set<list<string> > x; // C++ requires a space in > >. set< list<string> > x; // You may optionally make use // symmetricspacing in < <.
17. 垂直留白(Vertical Whitespace)垂直留白越少越好。 這不僅僅是規(guī)則而是原則問題了:不是非常有必要的話就不要使用空行。尤其是:不要在兩個(gè)函數(shù)定義之間空超過2行,函數(shù)體頭、尾不要有空行,函數(shù)體中也不要隨意添加空行。 基本原則是:同一屏可以顯示越多的代碼,程序的控制流就越容易理解。當(dāng)然,過于密集的代碼塊和過于疏松的代碼塊同樣難看,取決于你的判斷,但通常是越少越好。 函數(shù)頭、尾不要有空行: void Function() {
// Unnecessaryblank lines before and after
}
代碼塊頭、尾不要有空行: while (condition) { // Unnecessaryblank line after
} if (condition) {
// Unnecessaryblank line before }
if-else塊之間空一行還可以接受: if (condition) { // Some lines ofcode too small to move to another function, // followed by ablank line.
} else { // Another blockof code }
______________________________________ 譯者:首先說明,對(duì)于代碼格式,因人、因系統(tǒng)各有優(yōu)缺點(diǎn),但同一個(gè)項(xiàng)目中遵循同一標(biāo)準(zhǔn)還是有必要的: 1. 行寬原則上不超過80列,把22寸的顯示屏都占完,怎么也說不過去; 2. 盡量不使用非ASCII字符,如果使用的話,參考UTF-8格式(尤其是UNIX/Linux下,Windows下可以考慮寬字符),盡量不將字符串常量耦合到代碼中,比如獨(dú)立出資源文件,這不僅僅是風(fēng)格問題了; 3. UNIX/Linux下無條件使用空格,MSVC的話使用Tab也無可厚非; 4. 函數(shù)參數(shù)、邏輯條件、初始化列表:要么所有參數(shù)和函數(shù)名放在同一行,要么所有參數(shù)并排分行; 5. 除函數(shù)定義的左大括號(hào)可以置于行首外,包括函數(shù)/類/結(jié)構(gòu)體/枚舉聲明、各種語句的左大括號(hào)置于行尾,所有右大括號(hào)獨(dú)立成行; 6. ./->操作符前后不留空格,*/&不要前后都留,一個(gè)就可,靠左靠右依各人喜好; 7. 預(yù)處理指令/命名空間不使用額外縮進(jìn),類/結(jié)構(gòu)體/枚舉/函數(shù)/語句使用縮進(jìn); 8. 初始化用=還是()依個(gè)人喜好,統(tǒng)一就好; 9. return不要加(); 10. 水平/垂直留白不要濫用,怎么易讀怎么來。 公司 C++編程風(fēng)格指南(八)[完]規(guī)則之例外前面說明的編碼習(xí)慣基本是強(qiáng)制性的,但所有優(yōu)秀的規(guī)則都允許例外。 1. 現(xiàn)有不統(tǒng)一代碼(Existing Non-conformant Code)對(duì)于現(xiàn)有不符合既定編程風(fēng)格的代碼可以網(wǎng)開一面。 當(dāng)你修改使用其他風(fēng)格的代碼時(shí),為了與代碼原有風(fēng)格保持一致可以不使用本指南約定。如果不放心可以與代碼原作者或現(xiàn)在的負(fù)責(zé)人員商討,記住,一致性包括原有的一致性。 1. Windows代碼(Windows Code)Windows程序員有自己的編碼習(xí)慣,主要源于Windows的一些頭文件和其他Microsoft代碼。我們希望任何人都可以順利讀懂你的代碼,所以針對(duì)所有平臺(tái)的C++編碼給出一個(gè)單獨(dú)的指導(dǎo)方案。 如果你一直使用Windows編碼風(fēng)格的,這兒有必要重申一下某些你可能會(huì)忘記的指南(譯者注,我怎么感覺像在被洗腦:D): 1) 不要使用匈牙利命名法(Hungarian notation,如定義整型變量為iNum),使用公司命名約定,包括對(duì)源文件使用.cc擴(kuò)展名; 2) Windows定義了很多原有內(nèi)建類型的同義詞(譯者注,這一點(diǎn),我也很反感),如DWORD、HANDLE等等,在調(diào)用Windows API時(shí)這是完全可以接受甚至鼓勵(lì)的,但還是盡量使用原來的C++類型,例如,使用const TCHAR *而不是LPCTSTR; 3) 使用Microsoft Visual C++進(jìn)行編譯時(shí),將警告級(jí)別設(shè)置為3或更高,并將所有warnings當(dāng)作errors處理; 4) 不要使用#pragma once;作為包含保護(hù),使用C++標(biāo)準(zhǔn)包含保護(hù),包含保護(hù)的文件路徑包含到項(xiàng)目樹頂層(譯者注,#include<prj_name/public/tools.h>); 5) 除非萬不得已,否則不使用任何不標(biāo)準(zhǔn)的擴(kuò)展,如#pragma和__declspec,允許使用__declspec(dllimport)和__declspec(dllexport),但必須通過DLLIMPORT和DLLEXPORT等宏,以便其他人在共享使用這些代碼時(shí)容易放棄這些擴(kuò)展。 在Windows上,只有很少一些偶爾可以不遵守的規(guī)則: 1) 通常我們禁止使用多重繼承,但在使用COM和ATL/WTL類時(shí)可以使用多重繼承,為了執(zhí)行COM或ATL/WTL類及其接口時(shí)可以使用多重實(shí)現(xiàn)繼承; 2) 雖然代碼中不應(yīng)使用異常,但在ATL和部分STL(包括Visual C++的STL)中異常被廣泛使用,使用ATL時(shí),應(yīng)定義_ATL_NO_EXCEPTIONS以屏蔽異常,你要研究一下是否也屏蔽掉STL的異常,如果不屏蔽,開啟編譯器異常也可以,注意這只是為了編譯STL,自己仍然不要寫含異常處理的代碼; 3) 通常每個(gè)項(xiàng)目的每個(gè)源文件中都包含一個(gè)名為StdAfx.h或precompile.h的頭文件方便頭文件預(yù)編譯,為了使代碼方便與其他項(xiàng)目共享,避免顯式包含此文件(precompile.cc除外),使用編譯器選項(xiàng)/FI以自動(dòng)包含; 4) 通常名為resource.h、且只包含宏的資源頭文件,不必拘泥于此風(fēng)格指南。 團(tuán)隊(duì)合作參考常識(shí),保持一致。 編輯代碼時(shí),花點(diǎn)時(shí)間看看項(xiàng)目中的其他代碼并確定其風(fēng)格,如果其他代碼if語句中使用空格,那么你也要使用。如果其中的注釋用星號(hào)(*)圍成一個(gè)盒子狀,你也這樣做: /********************************** * Some comments are here. * There may be many lines. **********************************/
編程風(fēng)格指南的使用要點(diǎn)在于提供一個(gè)公共的編碼規(guī)范,所有人可以把精力集中在實(shí)現(xiàn)內(nèi)容而不是表現(xiàn)形式上。我們給出了全局的風(fēng)格規(guī)范,但局部的風(fēng)格也很重要,如果你在一個(gè)文件中新加的代碼和原有代碼風(fēng)格相去甚遠(yuǎn)的話,這就破壞了文件本身的整體美觀也影響閱讀,所以要盡量避免。 好了,關(guān)于編碼風(fēng)格寫的差不多了,代碼本身才是更有趣的,盡情享受吧!
完整的pdf格式文檔51黑下載地址(共58頁):
c編程風(fēng)格(高清版).pdf
(763.17 KB, 下載次數(shù): 12)
2023-4-17 04:30 上傳
點(diǎn)擊文件名下載附件
下載積分: 黑幣 -5
|