C / C ++項目代碼規範,谷歌開源代碼規範,個人推薦

 google開源 C / C ++項目代碼規範 谷歌開源項目開源指南。

1.頭文件

每通常一個  .cc 文件都有一個對應的  .h 文件。也有一些常見例外,如單元測試代碼和只包含 main() 函數的  .cc 文件。

正確使用頭文件可令代碼在可讀性,文件大小和性能上大爲改觀。

下面的規則將引導你規避使用頭文件時的各種陷阱。

1.1. 自包含的頭文件

TIP

頭文件應該能夠自給自足(自包含的,也就是可以作爲第一個頭文件被引入),以  .h 結尾。至於用來插入文本的文件,說到底它們並不是頭文件,所以以應  .inc 結尾不允許。出分離 -inl.h 頭文件的做法。

所有頭文件要能夠自給自足。換言之,用戶和重構工具不需要爲特別場合而包含額外的頭文件。詳言之,一個頭文件要有  1.2。#define保護,統計包含它所需要的其它頭文件,也不要求定義任何特別的符號。

不過有一個例外,即一個文件並不是自足的,而是作爲文本插入到代碼某處。或者,文件內容實際上是其它頭文件的特定平臺(特定於平臺)擴展部分,這些文件就要用  .inc 文件擴展名。

如果  .h 文件聲明瞭一個模板或內聯函數,同時也在該文件加以定義。有用凡是到這些的  .cc 文件,就得統統包含該頭文件,否則程序可能會在構建中鏈接失敗。不要把這些定義放到分離的 -inl.h 文件裏(譯者注:過去該規範曾提倡把定義放到-inl.h裏過)。

有個例外:如果某函數模板爲所有相關模板參數顯式實例化,或本身就是某類的一個私有成員,它就那麼定義只能在實例化該模板的  .cc 文件裏。

1.2. #define保護

TIP

所有頭文件都應該使用  #define 來防止頭文件被多重包含,命名格式當是: <PROJECT>_<PATH>_<FILE>_H_ 。

爲保證唯一性,頭文件的命名應該基於所有項目源代碼樹的全路徑。例如,項目  foo 中的頭文件 foo/src/bar/baz.h 可按如下方式保護:

#ifndef FOO_BAR_BAZ_H_ 
#define FOO_BAR_BAZ_H_ 
... 
#endif // FOO_BAR_BAZ_H_

1.3. 前置聲明

TIP

儘可能地避免使用前置聲明。使用  #include 所有遊戲需要的頭文件即可。

定義:

所謂「前置聲明」(forward declaration)是類,函數和模板的純粹聲明,沒伴隨着其定義。

優點:

  • 前置聲明能夠節省編譯時間,的多餘  #include 會迫使compile-器展開更多的文件,處理更多的輸入。
  • 前置聲明能夠節省不必要的重新編譯的時間。  #include 使代碼因爲頭文件中無關的改動而被重新編譯多次。

缺點:

  • 前置聲明隱藏了依賴關係,頭文件改動時,用戶的代碼會跳過必要的重新編譯過程。

  • 前置聲明可能會被庫的後續更改所破壞。前置聲明函數或模板有時會妨礙頭文件開發者變動其API。例如擴大形參類型,加個自帶默認參數的模板形參等等。

  • 前置聲明來自命名空間  std:: 的符號時,其行爲未定義。

  • 很難判斷什麼時候該用前置聲明,時候什麼用該  #include 極端情況下,用前置聲明代替。 includes 甚至都會暗暗地改變代碼的含義:

    // bh:
    struct  B  {}; 
    struct  D  : B  {}
    
    // good_user.cc: 
    #包括 “BH”
    void  f (B * ); 
    void  f (void * ); 
    void  test (D *  x ) {  f (x );  }   //調用f(B *)
    
如果  #include 被  B 狀語從句:  D 的前置聲明替代,  test() 就會調用  f(void*) 。
  • 前置聲明瞭include 不少來自頭文件的符號時,就會比單單一行的  冗長。
  • 僅僅爲了能前置聲明而重構代碼(比如用指針成員代替對象成員)會使代碼變得更慢更復雜。

結論:

  • 儘量避免前置聲明那些定義在其他項目中的實體。
  • 函數:總是使用  #include
  • 類模板:優先使用  #include

至於什麼時候包含頭文件,參見  1.5。#include的路徑及順序  。

1.4. 內聯函數

TIP

只有當函數只有10行甚至更少時纔將其定義爲內聯函數。

定義:

當函數被聲明爲內聯函數之後,編譯器會將其內聯展開,而不是按通常的函數調用機制進行調用。

優點:

只要內聯的函數體小小,內聯該函數可以令目標代碼更加高效。對於存取函數以及其它函數體比較短,性​​能關鍵的函數,鼓勵使用內聯。

缺點:

濫用內聯將導致程序變得更慢。內聯可能使目標代碼量或增或減,這取決於內聯函數的大小。內聯非常短小的存取函數通常會減少代碼大小,但內聯一個相當大的函數將戲劇性的增加代碼大小。現代處理器由於更好的利用了指令緩存,小巧的代碼往往執行更快。

結論:

一個較爲合理的經驗準則是,不要內聯超過10行的函數。謹謹對待析構函數,析構函數往往比其表面看起來要更長,因爲有隱含的成員和基類析構函數被調用!

另一個實用的經驗準則:內聯那些包含循環或  switch 語句的函數常常是得不償失(除非在大多數情況下,這些循環或  switch 語句從不被執行)。

有些函數即使聲明爲內聯的也不一定會被編譯器內聯,這點很重要; 比如虛函數和遞歸函數就不會被正常內聯。通常,遞歸函數不應該聲明成內聯函數。(YuleFox注:遞歸調用堆棧的展開並不像循環那麼簡單,比如遞進層數在編譯時可能是未知的,大多數編譯器都不支持內聯遞歸函數)。虛函數內聯的主要原因是想把它的函數體放在類定義內,爲了圖個方便,抑或是當作文件描述其行爲,比如精短的存取函數。

1.5.  #include 的路徑及順序

TIP

使用標準的頭文件包含順序可增強可讀性,避免隱藏依賴:相關頭文件,C庫,C ++庫,其他庫的  .h,本項目內的  .h。

項目內部文件應按照項目源代碼目錄樹結構排列,避免使用UNIX特殊的快捷目錄  .(當前目錄)或  .. (上級目錄)。例如,  google-awesome-project/src/base/logging.h 應該按如下方式包含:

#include  “base / logging.h”

又如,  dir/foo.cc 或  dir/foo_test.cc 的主要作用英文的英文實現或測試  dir2/foo2.h 的功能, foo.cc 中包含頭文件的次序如下:

  1. dir2/foo2.h (優先位置,詳情如下)
  2. C系統文件
  3. C ++系統文件
  4. 庫其他的  .h 文件
  5. 項目本。內  .h 文件

優先這種順序的排序保證當  dir2/foo2.h 遺漏某些必要的庫時,  dir/foo.cc 或 dir/foo_test.cc 的構建會立刻中止。因此這一條規則保證維護這些文件的人們首先看到構建中止的消息而不是維護其他包的人們。

dir/foo.cc 和  dir2/foo2.h 通常位於同一目錄下(如  base/basictypes_unittest.cc 和 base/basictypes.h),但也可放在不同目錄下。

按字母順序分別對每種類型的頭文件進行二次排序是不錯的主意。注意較老的代碼可不符合這條規則,要在方便的時候改正它們。

您所依賴的符號(符號)被哪些頭文件所定義,您就應該包含(包括)哪些頭文件,前置聲明  (向前聲明)情況除外。您比如要用到  bar.h 中的某個符號,哪怕您所包含的  foo.h 已經包含了 bar.h,也照樣得包含  bar.h,除非foo.h 有明確  說明它會自動向您提供  bar.h 中符號。不過,凡是cc文件所對應的「相關頭文件」已經包含的,就不用再重複包含進其cc文件裏面了,就像 foo.cc 只包含  foo.h就夠了,不用再管後者所包含的其它內容。

舉#include “foo / public / fooserver.h”//優先位置

#include “foo / public / bar.h”例如,   google-awesome-project/src/foo/internal/fooserver.cc 包含次序如下:


 

4.函數

4.1. 參數順序

總述

函數的參數順序爲:輸入參數在先,後跟輸出參數。

說明

C / C ++中的函數參數或者是函數的輸入,或者是函數的輸出,或兼而有之。輸入參數通常是值參或 const 引用,輸出參數或輸入/輸出參數則一般爲非  const 指針。在排列參數順序時,將所有的輸入參數置於輸出參數之前。特別要注意,在加入新參數時不要因爲它們是新參數就置於參數列表最後,而是仍然要按照前述的規則,即將新的輸入參數也置於輸出參數之前。

這並非一個硬性規定。輸入/輸出參數(通常是類或結構體)讓這個問題變得複雜。並且,有時候爲了其他函數保持一致,你可能不得不不所有變通。

4.2. 編寫簡短函數

總述

我們傾向於編寫簡短,凝練的函數。

說明

我們承認長函數有時是合理的,因此並不硬限制函數的長度。如果函數超過40行,可以思索一下能不能在不影響程序結構的前提下對其進行分割。

即使一個長函數現在工作的非常好,一旦有人對其修改,有可能出現新的問題,甚至導致難以發現的錯誤。使函數儘量簡短,以便於他在他人閱讀和修改代碼。

在處理代碼時,你可能會發現複雜的長函數。不要害怕修改現有代碼:如果證實這些代碼使用/調試起來很困難,或者你只需​​要使用其中的一小段代碼,考慮將其分割爲更加簡短並易於管理的若干函數。

4.3. 引用參數

總述

所有按引用傳遞的參數必須加上  const

定義

在C語言中,如果函數需要修改變量的值,參數必須爲指針,如  。在C ++中,函數還可以聲明爲引用參數:  。int foo(int *pval)int foo(int &val)

優點

引用定義參數可以防止出現  (*pval)++ 這樣醜陋的代碼。引用參數對於拷貝構造函數這樣的應用也是必需的。同時也更明確地不接受空指針。

缺點

容易引起誤解,因爲引用在語法上是值變量卻擁有指針的語義。

結論

函數參數列表中,所有引用參數都必須是  const

void  Foo (const  string  &in , string  * out );

事實上這在Google Code是一個硬性約定:輸入參數是值參或  const 引用,輸出參數爲指針。輸入參數可以是  const 指針,但決不能是非  const 引用參數,除非特殊要求,比如  swap()

有時候,在輸入形參中用針指   比   更明智。比如:const T*const T&

  • 可能會傳遞空指針。
  • 函數要把指針或對地址的引用賦值給輸入形參。

總而言之,大多時候輸入形參往往是  。用若   則說明輸入側另有處理。所以若要使用  ,則應給出相應的理由,否則會使讀者感到迷惑。const T&const T*const T*

4.4. 函數重載

總述

若要使用函數重載,則必須能讓讀者一看調用點就胸有成竹,而不用花心思猜測調用的重載函數到底是哪一種。這一規則也適用於構造函數。

定義

你可以編寫一個參數類型爲   的函數,然後用另一個參數類型爲   的函數對其進行重載:const string&const char*

class  MyClass  { 
    public :
    void  Analyze (const  string  &text ); 
    void  分析(const  char  * text , size_t  textlen ); 
};

優點

通過重載參數不同的同名函數,可以令代碼更直觀。模板化代碼需要重載,這同時也能爲使用者帶來便利。

缺點

如果函數單靠不同的參數類型而重載(acgtyrant注:這意味着參數數量不變),讀者就得十分熟悉C ++五花八門的匹配規則,以瞭解匹配過程具體到底如何。另外,如果派生類只重載了某個函數的部分變體,繼承語義就容易令人困惑。

結論

如果打算重載一個函數,可以試試改在函數名里加參數信息。例如,用  AppendString()和 AppendInt() 等,而不是一口氣重載多個  Append()。如果重載函數的目的是爲了支持不同數量的同一類型參數,則優先考慮使用  std::vector 以便使用者可以用  列表初始化指定參數。

4.5. 缺省參數

總述

只允許在非虛函數中使用缺省參數,且必須保證缺省參數的值始終一致。參數缺省與  函數重載  遵循同樣的規則。一般情況下建議使用函數重載,尤其是在缺省函數帶來的可讀性提升不能彌補下文中所提到的缺點的情況下。

優點

有些函數一般情況下使用默認參數,但有時需要又使用非默認的參數。缺省參數爲這樣的情形提供了便利,使程序員不需要爲了極少的例外情況編寫大量的函數。和函數重載相比,缺省參數的語法更簡潔明瞭,減少了大量的樣板代碼,也更好地區別了“必要參數”和“可選參數”。

缺點

缺省參數實際上是函數重載語義的另一種實現方式,因此所有  不應當使用函數重載的理由  也都適用於缺省參數。

虛函數調用的缺省參數取決於目標對象的靜態類型,此時無法保證給定函數的所有重載聲明的都是同樣的缺省參數。

缺省參數是在每個調用點都要進行重新求值的,這會造成生成的代碼迅速膨脹。作爲讀者,一般來說也更希望缺省的參數在聲明時就已經被固定了,而不是在每次調用時都可能會有不同的取值。

缺省參數會干擾函數指針,導致函數簽名與調用點的簽名不一致。而函數重載不會導致這樣的問題。

結論

對於虛函數,不允許使用缺省參數,因爲在虛函數中缺省參數不一定能正常工作。如果在每個調用點缺省參數的值都有可能不同,在這種情況下缺省函數也不允許使用。(例如,不要寫像   這樣的代碼。)void f(int n = counter++);

在其他情況下,如果缺省參數對可讀性的提升遠遠超過了以上提及的缺點的話,可以使用缺省參數。如果仍有疑惑,就使用函數重載。

4.6. 函數返回類型後置語法

總述

只有在常規寫法(返回類型前置)不便於書寫或不便於閱讀時使用返回類型後置語法。

定義

C ++現在允許兩種不同的函數聲明方式。以往的寫法是將返回類型置於函數名之前。例如:

int  foo (int  x );

C ++ 11引入了這一新的形式。現在可以在函數名前使用  auto 關鍵字,在參數列表之後後置返回類型。例如:

auto  foo (int  x ) - >  int ;

後置返回類型爲函數作用域。對於像  int 這樣簡單的類型,兩種寫法沒有區別。但對於複雜的情況,例如類域中的類型聲明或者以函數參數的形式書寫的類型,寫法的不同會造成區別。

優點

後置返回類型是顯式地指定  Lambda表達式  的返回值的唯一方式。某些情況下,編譯器可以自動推導出Lambda表達式的返回類型,但並不是在所有的情況下都能實現。即使編譯器能夠自動推導,顯式地指定返回類型也能讓讀者更明瞭。

有時在已經出現了的函數參數列表之後指定返回類型,能夠讓書寫更簡單,也更易讀,尤其是在返回類型依賴於模板參數時。例如:

template  < class  T , class  U >  auto  add (T  t , U  u ) - >  decltype (t  +  u );

對比下面的例子:

template  < class  T , class  U >  decltype (declval < T &> () +  declval < U &gt ;) add (T  t , U  u );

缺點

後置返回類型相對來說是非常新的語法,而且在C和Java中都沒有相似的寫法,因此可能對讀者來說比較陌生。

在已有的代碼中有大量的函數聲明,你不可能把它們都用新的語法重寫一遍。因此實際的做法只能是使用舊的語法或者新舊混用。在這種情況下,只使用一種版本是相對來說更規整的形式。

結論

在大部分情況下,應當繼續使用以往的函數聲明寫法,即將返回類型置於函數名前。只有在必要的時候(如Lambda表達式)或者使用後置語法能夠簡化書寫並且提高易讀性的時候才使用新的返回類型後置語法。但是後一種情況一般來說是很少見的,大部分時候都出現在相當複雜的模板代碼中,而多數情況下不鼓勵寫這樣  複雜的模板代碼

 

7.命名約定

最重要的一致性規則是命名管理。命名的風格能讓我們在不需要去查找類型聲明的條件下快速地瞭解某個名字代表的含義:類型,變量,函數,常量,宏,等等,甚至。我們大腦中的模式匹配引擎非常依賴這些命名規則。

命名規則具有一定隨意性,但相比按個人喜好命名,一致性更重要,所以無論你認爲它們是否重要,規則總歸是規則。

7.1. 通用命名規則

總述

函數命名,變量命名,文件命名要有描述性; 少用縮寫。

說明

儘可能使用描述性的命名,別心疼空間,畢竟相比之下讓代碼易於新讀者理解更重要。不要用只有項目開發者能理解的縮寫,也不要通過砍掉幾個字母來縮寫單詞。

int  price_count_reader ;     //無縮寫
int  num_errors ;             //“num”是一個常見的寫法
int  num_dns_connections ;    //人人都知道“DNS”是什麼
int  n ;                      //毫無意義。
int  nerr ;                   //含糊不清的縮寫。
int  n_comp_conns ;           //含糊不清的縮寫。
int  wgc_connections ;        //只有貴團隊知道是什麼意思 
int  pc_reader ;              //“pc”有太多可能的解釋了。
int  cstmr_id ;               //刪減了若干字母。

注意,一些特定的廣爲人知的縮寫是允許的,例如用  i 表示迭代變量和用  T 表示模板參數。

模板參數的命名應當遵循對應的分類:類型模板參數應當遵循  類型命名  的規則,而非類型模板應當 遵循變量命名  的規則。

7.2. 文件命名

總述

文件名要全部小寫,可以包含下劃線(_)或連-字符(),依照項目的約定。如果沒有約定,那麼“ _” 更好。

說明

可接受的文件命名示例:

  • my_useful_class.cc
  • my-useful-class.cc
  • myusefulclass.cc
  • myusefulclass_test.cc //  _unittest 狀語從句:  _regtest 已棄用。

C ++文件要以  .cc 結尾,頭文件以  .h 結尾。專門插入文本的文件則以  .inc 結尾,參見  頭文件自足

不要使用已經存在於  /usr/include 下的文件名(Yang.Y注:即編譯器搜索系統頭文件的路徑),如 db.h

通常應儘量讓文件名更加明確。  http_server_logs.h 就比  logs.h 要好。定義類時文件名一般成對出現,如  foo_bar.h 和  foo_bar.cc,對應於類  FooBar

聯內必須函數放在  .h 文件中。如果內聯函數比較短,就直接放在  .h 中。

7.3. 類型命名

總述

類型名稱的每個單詞首字母均大寫,不包含下劃線:  MyExcitingClass,  MyExcitingEnum

說明

所有類型命名 - 類,結構體,類型定義(typedef),枚舉,類型模板參數 - 均使用相同約定,即以大寫字母開始,每個單詞首字母均大寫,不包含下劃線。例如:

//類和結構體
類 UrlTable  {  ... 
class  UrlTableTester  {  ... 
struct  UrlTableProperties  {  ...

//類型定義
typedef  hash_map < UrlTableProperties  * , string >  PropertiesMap ;

//使用別名
使用 PropertiesMap  =  hash_map < UrlTableProperties  * , string > ;

//枚舉
enum  UrlTableErrors  {  ...

7.4. 變量命名

總述

變量(包括函數參數)和數據成員名一律小寫,單詞之間用下劃線連接。類的成員變量以下劃線結尾,但結構體的就不用,如:  a_local_variable,  a_struct_data_member, a_class_data_member_

說明

普通變量命名

舉例:

字符串 table_name ;   //好 - 用下劃線。
字符串 表名;    //好 - 全小寫。

字符串 tableName ;   //差 - 混合大小寫

類數據成員

不管是靜態的還是非靜態的,類數據成員都可以和普通變量一樣,但要接下劃線。

類 TableInfo  { 
  ... 
 private :
  string  table_name_ ;   //好 - 後加下劃線。
  字符串 tablename_ ;    //好。
  靜態 池< TableInfo > *  pool_ ;   //好。
};

結構體變量

不管是靜態的還是非靜態的,結構體數據成員都可以和普通變量一樣,不用像類那樣接下劃線:

struct  UrlTableProperties  { 
  string  name ; 
  int  num_entries ; 
  靜態 池< UrlTableProperties > *  池; 
};

結構體與類的使用討論,參考  結構體與類

7.5. 常量命名

總述

聲明爲  constexpr 或  const 的變量,或在程序運行期間其值始始保持不變的,命名時以“k”開頭,大小寫混合。例如:

const  int  kDaysInAWeek  =  7 ;

說明

所有具有靜態存儲類型的變量(例如靜態變量或全局變量,參見  存儲類型)都應當以此方式命名。對於其他存儲類型的變量,如自動變量等,這條規則是可選的。如果不採用這條規則,就按照一般的變量命名規則。

7.7. 函數命名

總述

常規函數使用大小寫混合,取值和設值函數則要求與變量名匹配:  MyExcitingFunction(), MyExcitingMethod(),  my_exciting_member_variable(),  set_my_exciting_member_variable()

說明

一般來說,函數名的每個單詞首字母大寫(即“駝峯變量名”或“帕斯卡變量名”),沒有下劃線。對於首字母縮寫的單詞,更傾向於將它們視作一個單詞進行首字母大寫(例如,寫作 StartRpc() 而非  StartRPC())。

AddTableEntry ()
DeleteUrl ()
OpenFileOrDie ()

(同樣的命名規則同時適用於類作用域和命名空間作用域的常量,因爲它們是作爲API的一部分暴露對外的,因此應當讓它們看起來像是一個函數,因爲在這時,它們實際上是一個對象而非函數的這一事實對外不過是一個無關緊要的實現細節。)

取值和設值函數的命名與變量一致。一般來說它們的名稱與實際的成員變量對應,但並不強制要求。例如   與  。int count()void set_count(int count)

7.7. 命名空間命名

總述

命名空間以小寫字母命名。最高級命名空間的名字取決於項目名稱。要注意避免嵌套命名空間的名字之間和常見的頂級命名空間的名字之間發生衝突。

頂級命名空間的名稱應當是項目名或者是該命名空間中的代碼所屬的團隊的名字。命名空間中的代碼,應當存放於和命名空間的名字匹配的文件夾或其子文件夾中。

注意  不使用縮寫作爲名稱  的規則同樣適用於命名空間。命名空間中的代碼極少需要涉及命名空間的名稱,因此沒有必要在命名空間中使用縮寫。

要避免嵌套的命名空間與常見的頂級命名空間發生名稱衝突。由於名稱查找規則的存在,命名空間之間的衝突完全有可能導致編譯失敗。尤其是,不要創建嵌套的  std 命名空間。建議使用更獨特的項目標識符(websearch::index,  websearch::index_util)而非常見的極易發生衝突的名稱(比如 websearch::util)。

對於  internal 命名空間,要當心加入到同一  internal 命名空間的代碼之間發生衝突(由於內部維護人員通常來自同一團隊,因此常有可能導致衝突)。在這種情況下,請使用文件名以使內部名稱獨一無二(例如對於  frobber.h,使用  websearch::index::frobber_internal)。

7.8. 枚舉命名

總述

的枚舉命名應當狀語從句:  常量  或    harmony和諧:  kEnumName 或是  ENUM_NAME

說明

的單獨枚舉值應該優先採用  常量  的命名方式。但    方式的命名也。可以接受。枚舉名 UrlTableErrors (以及  AlternateUrlTableErrors)是類型,所以要用大小寫混合的方式。

enum  UrlTableErrors  { 
    kOK  =  0 ,
    kErrorOutOfMemory ,
    kErrorMalformedInput ,
}; 
枚舉 AlternateUrlTableErrors  { 
    OK  =  0 ,
    OUT_OF_MEMORY  =  1 ,
    MALFORMED_INPUT  =  2 ,
};

2009年1月之前,我們一直建議採用    的方式命名枚舉值。由於枚舉值和宏之間的命名衝突,直接導致了很多問題。由此,這裏改爲優先選擇常量風格的命名方式。新代碼應該儘可能優先使用常量風格。但是老代碼沒必要切換到常量風格,除非宏風格確實會產生編譯期問題。

7.9. 宏命名

總述

你並不打算  使用宏,對吧?如果你一定要用,像這樣命名:  MY_MACRO_THAT_SCARES_SMALL_CHILDREN

說明

參考  預處理宏 ; 通常  不應該  使用宏。如果不得不使用,其命名像枚舉命名一樣全部大寫,使用下劃線:

#define ROUND(x)... 
#define PI_ROUNDED 3.0

7.10. 命名規則的特例

總述

如果你命名的實體與已有C / C ++實體相似,可參考現有命名策略。

bigopen():函數名,參照  open() 的形式

uint: typedef

bigpos:  struct 或  class,參照  pos 的形式

sparse_hash_map:STL型實體; 參照STL命名約定

LONGLONG_MAX:常量,如同 INT_MAX

8.注意

註釋雖然寫起來很痛苦,但對保證代碼可讀性至關重要。下面的規則描述瞭如何註釋以及在哪兒註釋。當然也要記住:註釋固然很重要,但最好的代碼應當本身就是文檔。有意義的類型名和變量名,要遠勝過要用註釋解釋的含糊不清的名字。

你寫的註釋是給代碼讀者看的,也就是下一個需要理解你的代碼的人。所以慷慨些吧,下一個讀者可能就是你!

8.1. 註釋風格

總述

使用  // 或  ,統一就好。/* */

說明

// 或   都可以; 但  更  常用。要在如何註釋及註釋風格上確保統一。/* */// 

8.2. 文件註釋

總述

在每一個文件開頭加入版權公告。

文件註釋描述了該文件的內容。如果一個文件只聲明,或實現或測試了一個對象,並且這個對象已經在它的聲明處進行了詳細的註釋,那麼就沒有必要再加上文件註釋。除此之外的其他文件都需要文件註釋。

說明

法律公告和作者信息

每個文件都應該包含許可證引用。爲項目選擇合適的許可證版本(比如,Apache 2.0,BSD,LGPL,GPL)

如果你對原始作者的文件做了重大修改,請考慮刪除原作者信息。

文件內容

如果一個  .h 文件聲明瞭多個概念,則文件註釋應當對文件的內容做一個大致的說明,同時說明各個概念之間的聯繫。一個一到兩行的文件註釋就足夠了,對於每個概念的詳細文檔應當放在各個概念中,而不是文件註釋中。

不要在  .h 和  .cc 之間複製註釋,這樣的註釋偏離了註釋的實際意義。

8.3. 類註釋

總述

每個類的定義都要附帶一份註釋,描述類的功能和用法,除非它的功能相當明顯。

//遍歷GargantuanTable的內容。
//示例:
// GargantuanTableIterator * iter = table-> NewIterator(); 
// it for(iter-> Seek(“foo”);!iter-> done(); iter-> Next()){ 
// process(iter-> key(),iter-> value()); 
//} 
//刪除它; 
類 GargantuanTableIterator  { 
  ... 
};

說明

類註釋應當爲讀者理解如何使用與何時使用類提供足夠的信息,同時應當提醒讀者在正確使用此類時應當考慮的因素。如果類有任何同步前提,請用文檔說明。如果該類的實例可被多線程訪問,要特別注意文檔說明多線程環境下相關的規則和常量使用。

如果你想用一小段代碼演示這個類的基本用法或通常用法,放在類註釋裏也非常合適。

如果類的聲明和定義分開了(例如分別放在了  .h 和  .cc 文件中),此時,描述類用法的註釋應當和接口定義放在一起,描述類的操作和實現的註釋應當和實現放在一起。

8.4. 函數註釋

總述

函數聲明處的註釋描述函數功能; 定義處的註釋描述函數實現。

說明

函數聲明

基本上每個函數聲明處前都應當加上註釋,描述函數的功能和用途。只有在函數的功能簡單而明顯時才能省略這些註釋(例如,簡單的取值和設值函數)。註釋使用敘述式(“打開文件”)而非指令式(“打開文件”); 註釋只是爲了描述函數,而不是命令函數做什麼。通常,註釋不會描述函數如何工作。那是函數定義部分的事情。

函數聲明處註釋的內容:

  • 函數的輸入輸出。
  • 對類成員函數而言:函數調用期間對象是否需要保持引用參數,是否會釋放這些參數。
  • 函數是否分配了必須由調用者釋放的空間。
  • 參數是否可以爲空指針。
  • 是否存在函數使用上的性能隱患。
  • 如果函數是可重入的,其同步提提是什麼?

舉例如下:

//返回此表的迭代器。
當迭代器完成時,它是
客戶端的責任//並且一旦
創建
迭代器的GargantuanTable對象被刪除,它就不能使用迭代器。// 
//迭代器最初位於表的開始位置。
// 
//此方法等同於:
// Iterator * iter = table-> NewIterator(); 
// iter-> Seek(“”); 
//返回iter; 
//如果您要立即尋找到
返回的迭代器
中的其他位置,則使用NewIterator()會更快,並避免額外的查找。
Iterator *  GetIterator () const;

但也要避免羅羅嗦嗦,或者對顯着易見的內容進行說明。下面的註釋就沒有必要加上“否則返回false”,因爲已經暗含其中了:

//如果表不能包含更多條目,則返回true。
bool  IsTableFull ();

註釋函數重載時,註釋的重點應該是函數中被重載的部分,而不是簡單的重複被重載的函數的註釋。多數情況下,函數重載不需要額外的文檔,因此也沒有必要加上註釋。

註釋構造/析構函數,切記讀代碼的人知道構造/析構函數的所有功能,所以“銷燬這一對象”這樣的註釋是沒有意義的。你應該注意的是注意構造函數對參數做了什麼(例如,是否取得指針所有權)以及析構函數清理了什麼。如果都是些無關緊要的內容,直接省掉註釋。析構函數前沒有註釋是很正常的。

函數定義

如果函數的實現過程中用到了很巧妙的方式,那麼在函數定義處應當加上解釋性的註釋。例如,你所使用的編程技巧,實現的大致步驟,或解釋如此實現的理由。舉個例子,你可以說明爲什麼函數的前半部分要加鎖而後半部分不需要。

不要  從  .h 文件或其他地方的函數聲明處直接複製註釋。簡要重述函數功能是可以的,但註釋重點要放在如何實現上。

8.5. 變量註釋

總述

通常變量名本身足以很好說明變量用途。某些情況下,也需要額外的註釋說明。

說明

類數據成員

每個類數據成員(也叫實例變量或成員變量)都應該用註釋說明用途。如果有非變量的參數(例如特殊值,數據成員之間的關係,生命週期等)不能夠使用類型與變量名明確表達,則應當加上註釋。然而,如果變量類型與變量名已經足夠描述一個變量,那麼就不需要加上註釋。

特別地,如果變量可以接受  NULL 或  -1 等警戒值,須加以說明。比如:

private :
 //用於限制檢查表訪問。-1意味着
 //我們還不知道表中有多少個條目。
 int  num_total_entries_ ;

全局變量

和數據成員一樣,所有全局變量也要註釋說明含義及用途,以及作爲全局變量的原因。比如:

//在此迴歸測試中我們經歷的測試用例的總數。
const  int  kNumTestCases  =  6 ;

8.6. 實現註釋

總述

對於代碼中巧妙的,晦澀的,有趣的,重要的地方加以註釋。

說明

代碼前註釋

巧妙或複雜的代碼段前要加註釋。比如:

//將結果除以2,考慮到x 
//包含來自add的進位。
for  (int  i  =  0 ;  i  <  result - > size ();  i ++ ) { 
  x  =  (x  <<  8 ) +  (* result )[ i ]; 
  (* 結果)[ i ]  =  x  >>  1 ; 
  x  &=  1 ; 
}

行註釋

比較隱晦的地方要在行尾加入註釋。在行尾空兩格進行註釋。比如:

//如果我們有足夠的內存,也可以對數據部分進行mmap。
mmap_budget  =  max < int64 > (0 , mmap_budget  -  index_ - > length ()); 
if  (mmap_budget  > =  data_size_  &&  !MmapData (mmap_chunk_bytes , mlock ))
  return ;   //錯誤已經記錄。

注意,這裏用了兩段註釋分別描述這段代碼的作用,並提示函數返回錯誤已經被記入日誌。

如果你需要連續進行多行註釋,可以使之對齊獲得更好的可讀性:

DoSomething ();                   //在這裏發表評論,以便評論排成一行。
DoSomethingElseThatIsLonger ();   //代碼和註釋之間有兩個空格。
{  //允許打開一個新的作用域時,在註釋之前的一個空格
  // //因此註釋與下面的註釋和代碼一起排列。
  DoSomethingElse ();   //通常在行註釋之前有兩個空格。
} 
std :: vector < string >  list { 
                    //支撐列表中的註釋描述下一個元素... 
                    “First item” ,
                    // ..並且應該適當地對齊。
“第二項” }; 
做一點事();  / *對於尾部塊註釋,一個空間可以。* /

函數參數註釋

如果函數參數的意義不明顯,考慮用下面的方式進行彌補:

  • 如果參數是一個字面常量,並且這一常量在多處函數調用中被使用,用以推斷它們一致,你應該用一個常量名讓這個約定變得更明顯,並且保證這一約定不會被打破。
  • 考慮更改函數的簽名,讓某個  bool 類型的參數變爲  enum 類型,這樣可以讓這個參數的值表達其意義。
  • 如果某個函數有多個配置選項,你可以考慮定義一個類或結構體以保存所有的選項,並傳入類或結構體的實例。這樣的方法有許多優點,例如這樣的選項可以在調用處用變量名引用,這樣就能清晰地表明其意義。同時也減少了函數參數的數量,使得函數調用更易讀也易寫。除此之外,以這樣的方式,如果你使用其他的選項,就無需對調用點進行更改。
  • 用具名變量代替大段而複雜的嵌套表達式。
  • 萬不得已時,才考慮在調用點用註釋闡明參數的意義。

比如下面的示例的對比:

//這些論據是什麼?
const  DecimalNumber  product  =  CalculateProduct (values , 7 , false , nullptr );

ProductOptions  選項; 
選項。set_precision_decimals (7 ); 
選項。set_use_cache (ProductOptions :: kDontUseCache ); 
const  DecimalNumber  product  = 
    CalculateProduct (values , options , / * completion_callback = * / nullptr );

哪個更清晰一目瞭然。

不允許的行爲

不要描述顯而易見的現象,  永遠不要  用自然語言翻譯代碼作爲註釋,除非即使對深入理解C ++的讀者來說代碼的行爲都是不明顯的。要假設讀代碼的人C ++水平比你高,即便他/她可能不知道你的用意:

你所提供的註釋應當解釋代碼  爲什麼  要這麼做和代碼的目的,或者最好是讓代碼自文檔化。

比較這樣的註釋:

//在矢量中查找元素。< - 差:這太明顯了!
自動 ITER  =  STD :: 找到(v 。開始(), v 。端(), 元素); 
如果 (ITER  =! v 。端()) { 
  過程(元件); 
}

和這樣的註釋:

//處理“元素”,除非它已經被處理。
自動 ITER  =  STD :: 找到(v 。開始(), v 。端(), 元素); 
如果 (ITER  =! v 。端()) { 
  過程(元件); 
}

自文檔化的代碼根本就不需要註釋。上面例子中的註釋對下面的代碼來說就是毫無必要的:

if  (!IsAlreadyProcessed (element )) { 
  Process (element ); 
}

8.8. 標點,拼寫和語法

總述

注意標點,拼寫和語法; 寫的好的註釋比差的要易讀的多。

說明

註釋的通常寫法是包含正確大小寫和結尾句號的完整敘述性語句。大多數情況下,完整的句子比句子片段可讀性更高。短一點的註釋,比如代碼行尾註釋,可以隨意點,但依然要注意風格的一致性。

雖然被別人指出該用分號時卻用了逗號多少有些尷尬,但清晰易讀的代碼還是很重要的。正確的標點,拼寫和語法對此會有很大幫助。

8.8. TODO註釋

總述

對那些臨時的,短期的解決方案,或已經夠好,但仍不完美的代碼使用  TODO 註釋。

TODO 注意要使用全大寫的字符串  TODO,在隨後的圓括號裏寫上你的名字,郵件地址,bug ID,或其它身份標識和與這一  TODO 相關的問題。主要目的是讓添加註釋的人(也是可以請求提供更多細節的人)可根據規範的  TODO 格式進行查找。添加  TODO 註釋並不意味着你要自己來修正,因此當你加上帶有姓名的時候  TODO ,一般都是寫上自己的名字。

// TODO([email protected]):這裏使用“*”作爲連接運算符。
// TODO(Zeke)將其改爲使用關係。
// TODO(錯誤12345):刪除“最後訪問者”功能

如果加  TODO 是爲了在“將來某一天做某事”,可以附上一個非常明確的時間“Fix by November 2005”),或者一個明確的事項(“所有客戶端都可以處理XML響應時刪除此代碼。”) 。

8.9. 棄用註釋

總述

通過棄用註釋(DEPRECATED 評論)以標記某接口點已棄用。

您可以寫上包含全大寫的  DEPRECATED 註釋,以標記某接口爲棄用狀態。註釋可以放在接口聲明前,或者同一行。

DEPRECATED 一詞後,在  括號中留下您的名字,郵箱地址以及其他身份標識。

棄用註釋應當包涵簡短而清晰的指引,以幫助其他人修復其調用點。在C ++中,你可以將一個棄用函數改造成一個內聯函數,這一函數將調用新的接口。

DEPRECATED 僅僅標記接口爲並  不允許大家不約而同地棄用,您還得親自主動修正調用點(callsites),或是找個幫手。

修正好的代碼應該不會再涉及棄用接口點了,着實改用新接口點。如果您不知從何下手,可以找標記棄用註釋的當事人一起商量。

9.格式

每個人都可能有自己的代碼風格和格式,但如果一個項目中的所有人都遵循同一風格的話,這個項目就能更順利地進行。每個人未必能同意下述的每一處格式規則,而且其中的不少規則需要一定時間的適應,但整個項目服從統一的編程風格是很重要的,只有這樣才能讓所有人輕鬆地閱讀和理解代碼。

爲了幫助你正確的格式化代碼,我們寫了一個  emacs配置文件

9.1. 行長度

總述

每一行代碼字符數不超過80。

我們也認識到這條規則是有爭議的,但很多已有代碼都遵照這一規則,因此我們感覺一致性更重要。

優點

提倡該原則的人認爲強迫他們調整編輯器窗口大小是很野蠻的行爲。很多人同時並排開幾個代碼窗口,根本沒有多餘的空間拉伸窗口。大家都把窗口最大尺寸加以限定,並且80列寬是傳統標準。那麼爲什麼要改變呢?

缺點

反對該原則的人則認爲更寬的代碼行更易閱讀。80列的限制是上個世紀60年代的大型機的古板缺陷;現代設備具有更寬的顯示屏,可以很輕鬆地顯示更多代碼。

結論

80個字符是最大值。

如果無法在不傷害易讀性的條件下進行斷行,那麼註釋行可以超過80個字符,這樣可以方便複製粘貼。例如,帶有命令示例或URL的行可以超過80個字符。

長所有遊戲的路徑  #include 語句可以超出80列。

文件頭保護  可以無視該原則。

9.2. 非ASCII字符

總述

儘量不使用非ASCII字符,使用時必須使用UTF-8編碼。

說明

即使是英文,也不應將用戶界面的文本硬編碼到源代碼中,因此非ASCII字符應當很少被用到。特殊情況下可以適當包含此類字符。例如,代碼分析外部數據文件時,可以適當硬編碼數據文件中作爲分隔符的非ASCII字符串; 更常見的是(不需要本地化的)單元測試代碼可能包含非ASCII字符串。此類情況下,應使用UTF-8編碼,因爲很多工具都可以理解和處理UTF-8編碼。

十六進制編碼也可以,能增強可讀性的情況下尤其鼓鼓 - 比如  "\xEF\xBB\xBF",或者更簡潔地寫作 u8"\uFEFF",在Unicode中是  零寬度無間斷  的間隔符號,如果不用十六進制直接放在UTF -8格式的源文件中,是看不到的。

(Yang.Y注:  "\xEF\xBB\xBF" 通常用作帶編碼標記的UTF-8)

使用  u8 前綴把帶  uXXXX 轉義序列的字符串字面值編碼成UTF-8。不要用在本身就帶UTF-8字符的字符串字面值上,因爲如果編譯器不把源代碼識別成UTF-8,輸出就會出錯。

別用C ++ 11的  char16_t 和  char32_t,它們和UTF-8文本沒有關係,  wchar_t 同理,除非你寫的代碼要調用Windows API,後者廣泛使用了  wchar_t

9.3. 空格還是製表位

總述

只使用空格,每次縮進2個空格。

說明

我們使用空格縮進。不要在代碼中使用製表符。你應該設置編輯器將製表符轉爲空格。

9.4. 函數聲明與定義

總述

返回類型和函數名在同一行,參數也儘量放在同一行,如果放不下就對形參分行,分行方式與  函數調用  一致。

說明

函數看上去像這樣:

ReturnType  ClassName :: FunctionName (Type  par_name1 , Type  par_name2 ) { 
  DoSomething (); 
  ... 
}

如果同一行文本太多,放不下所有參數:

ReturnType  ClassName :: ReallyLongFunctionName (類型 par_name1 , 類型 par_name2 ,
                                             類型 par_name3 ) { 
  DoSomething (); 
  ... 
}

甚至連第一個參數都放不下:

ReturnType  LongClassName :: ReallyReallyReallyLongFunctionName (
    Type  par_name1 ,  // 4 space indent 
    Type  par_name2 ,
    Type  par_name3 ) { 
  DoSomething ();   // 2空格縮進
  ... 
}

注意以下幾點:

  • 使用好的參數名。
  • 只有參數未被使用或者其用途非常明顯時,才能省略參數名。
  • 如果返回類型和函數名在一行放不下,分行。
  • 如果返回類型與函數聲明或定義分行了,不要縮進。
  • 左圓括號總是和函數名在同一行。
  • 函數名和左圓括號間永遠沒有空格。
  • 圓括號與參數間沒有空格。
  • 左大括號總在最後一個參數同一行的末尾處,不另起新行。
  • 右大括號總是單獨位於函數最後一行,或者與左大括號同一行。
  • 右圓括號和左大括號間總是有一個空格。
  • 所有形參應儘可能對齊。
  • 缺省縮進爲2個空格。
  • 換行後的參數保持4個空格的縮進。

未被使用的參數,或者根據上下文很容易看出其用途的參數,可以省略參數名:

類 美孚 { 
 公共:
  美孚(富&& ); 
  Foo (const  Foo &); 
  Foo & operator = (Foo && ); 
  Foo & operator = (const  Foo &); 
};

未被使用的參數如果其用途不明顯的話,在函數定義處將參數名註釋起來:

class  Shape  { 
 public :
  virtual  void  Rotate (double  radians ) =  0 ; 
};

class Circle : public Shape {
 public:
  void Rotate(double radians) override;
};

void Circle::Rotate(double /*radians*/) {}
// 差 - 如果將來有人要實現, 很難猜出變量的作用.
void Circle::Rotate(double) {}

屬性, 和展開爲屬性的宏, 寫在函數聲明或定義的最前面, 即返回類型之前:

MUST_USE_RESULT bool IsOK();

9.5. Lambda 表達式

總述

Lambda 表達式對形參和函數體的格式化和其他函數一致; 捕獲列表同理, 表項用逗號隔開.

說明

若用引用捕獲, 在變量名和 & 之間不留空格.

int x = 0;
auto add_to_x = [&x](int n) { x += n; };

短 lambda 就寫得和內聯函數一樣.

std::set<int> blacklist = {7, 8, 9};
std::vector<int> digits = {3, 9, 1, 8, 4, 7, 1};
digits.erase(std::remove_if(digits.begin(), digits.end(), [&黑名單](詮釋 我) { 
               返回 黑名單。找到(i ) !=  黑名單。end (); 
             }),
             數字。end ());

9.6. 函數調用

總述

要麼一行寫完函數調用,要麼在圓括號裏對參數分行,要麼參數另起一行且縮進四格。如果沒有其它顧慮的話,儘可能精簡行數,比如把多個參數適當地放在同一行裏。

說明

函數調用遵循如下形式:

bool  retval  =  DoSomething (argument1 , argument2 , argument3 );

如果同一行放不下,可斷爲多行,後面每一行都和第一個實參對齊,左圓括號後和右圓括號前不要留空格:

bool  retval  =  DoSomething (averyveryveryverylongargument1 ,
                          argument2 , argument3 );

參數也可以放在次行,縮進四格:

如果 (...) { 
  ... 
  ... 
  if  (...) { 
    DoSomething (
        argument1 , argument2 ,  // 4空格縮進
        argument3 , argument4 ); 
  }

把多個參數放在同一行以減少函數調用所需的行數,除非影響到可讀性。有人認爲把每個參數都獨立成行,不僅更好讀,而且方便編輯參數。不過,比起所謂的參數編輯,我們更看重可讀性,且後者比較好辦:

如果一些參數本身就是略複雜的表達式,且降低了可讀性,那麼可以直接創建臨時變量描述該表達式,並傳遞給函數:

int my_heuristic = scores[x] * y + bases[x];
bool retval = DoSomething(my_heuristic, x, y, z);

或者放着不管, 補充上註釋:

bool retval = DoSomething(scores[x] * y + bases[x],  // Score heuristic.
                          x, y, z);

如果某參數獨立成行, 對可讀性更有幫助的話, 那也可以如此做. 參數的格式處理應當以可讀性而非其他作爲最重要的原則.

此外, 如果一系列參數本身就有一定的結構, 可以酌情地按其結構來決定參數格式:

//通過3x3矩陣轉換小部件。
my_widget 。變換(x1 , x2 , x3 ,
                    y1 , y2 , y3 ,
                    z1 , z2 , z3 );

9.7. 列表初始化格式

總述

您平時怎麼格式化函數調用,就怎麼格式化  列表初始化

說明

如果列表初始化伴隨着名字,比如類型或變量名,格式化時將將名稱視圖函數調用名,  {}視圖函數調用的括號。如果沒有名字,就視作名字長度爲零。

//一行列表初始化示範。
返回 { foo , bar }; 
functioncall ({ foo , bar }); 
pair < int , int >  p { foo , bar };

//當不得不行時。
SomeFunction (
    { “在{” }   之前假定一個零長度的名字,//假設在{前有長度爲零的名字
    。some_other_function_parameter ); 
SomeType  變量{ 
    some , other , values ,
    { “假設在{” }   之前有一個零長度的名字,//假設在{前有長度爲零的名字。
    SomeOtherType { 
        “非常長的字符串需要周圍的中斷。” ,  //非常長的字符串,前後都需要斷行。
        一些, 其它 的值},
    SomeOtherType { “略短字符串” ,  //稍短的字符串。
                  一些, 其它, 值}}; 
SomeType  變量{ 
    “這太長了,無法將所有內容放在一行中” };   //字符串過長,因此無法放在同一行。
MyType  m  =  {   //注意了,您可以在{前斷行。
    superlongvariablename1 ,
    superlongvariablename2 ,
    { short , interior , list },
    { interiorwrappinglist ,
     interiorwrappinglist2 }};

9.9. 條件語句

總述

傾向於不在圓括號內使用空格。關鍵字  if 狀語從句:  else 另起一行。

說明

對基本條件語句有兩種可以接受的格式。一種在圓括號和條件之間有空格,另一種沒有。

最常見的是沒有空格的格式。哪一種都可以,最重要的是  保持一致。如果你是在修改一個文件,參考當前已有格式。如果是寫新的代碼,請參考目錄下或項目中的其它文件。還在猶豫的話,就不要加空格了。

if  (condition ) {   //圓括號裏沒有空格。
  ...   // 2空格縮進。
}  else  if  (...) {   // else與if的右括號同一行。
  ... 
}  其他 { 
  ... 
}

如果你更喜歡在圓括號內部加空格:

if  ( condition  ) {   //圓括號與空格緊鄰 - 不常見
  ...   // 2空格縮進。
}  else  {   // else與if的右括號同一行。
  ... 
}

所有注意下情況  if 狀語從句:左圓括號間都有個空格。右圓括號和左大括號之間也要有個空格:

if (condition )     //差 -  IF後面沒空格。
if  (condition ){    //差 -  {前面沒空格。
if (condition ){     //變本加厲地差。
if  (condition ) {   //好 -  IF和{都與空格緊鄰。

如果能增強可讀性,簡短的條件語句允許寫在同一行。當只有簡單語句並且沒有使用  else [主語]時使用:

if  (x  ==  kFoo ) 返回 新的 Foo (); 
if  (x  ==  kBar ) 返回 新的 Bar ();

如果有語句  else 分支則不允許:

//不允許 - 當有ELSE分支時如塊塊寫在同一行
if  (x ) DoThis (); 
其他 DoThat ();

通常,單行語句不需要使用大括號,如果你喜歡用也沒問題; 複雜的條件或循環語句用大括號可讀性會更好。有也。項目一些要求  if 必須總是使用大括號:

如果 (條件)
  DoSomething ();   // 2空格縮進。

if  (condition ) { 
  DoSomething ();   // 2空格縮進。
}

但如果語句中  if-else 某個分支使用了大括號的話,其它分支也必須使用:

//不可以這樣子 - 如果有大括號ELSE卻沒有。
if  (condition ) { 
  foo ; 
}  else 
  bar ;

//不可以這樣子 -  ELSE有大括號IF卻沒有。
如果 (條件)
  foo ; 
else  { 
  bar ; 
}
//只要其中一個分支用了大括號,兩個分支都要用上大括號。
if  (condition ) { 
  foo ; 
}  else  { 
  bar ; 
}

9.9. 循環和開關選擇語句

總述

switch 語句可以使用大括號分段,以表明cases之間不是連在一起的。在單語句循環裏,括號可用可不用。循環空應行業釋義體育使用  {} 或  continue

說明

switch 語句中的  case 塊可以使用大括號也可以不用,取決於你的個人喜好。如果用的話,要按照下文所述的方法。

如果有不滿足  case 條件的枚舉值,  switch 應該default 總是包含一個  匹配(如果有輸入值沒有case去處理,編譯器將給出警告)。如果  default 應該永遠執行不到,簡單的加條  assert

switch  (var ) { 
  case  0 : {   // 2空格縮進
    ...       // 4空格縮進
    break ; 
  } 
  案例 1 : { 
    ... 
    break ; 
  } 
  default : { 
    assert (false ); 
  } 
}

在單語句循環裏,括號可用可不用:

for  (int  i  =  0 ;  i  <  kSomeNumber ;  ++ i )
  printf (“我愛你\ n ” );

for  (int  i  =  0 ;  i  <  kSomeNumber ;  ++ i ) { 
  printf (“我拿回來\ n ” ); 
}

空循環體應使用  {} 或  continue,而不是一個簡單的分號。

while  (condition ) { 
  //反覆循環直到條件失效。
} 
for  (int  i  =  0 ;  i  <  kSomeNumber ;  ++ i ) {}   //可 - 空循環體。
同時 (條件) 繼續;   //可 -  contunue表明沒有邏輯。
while  (condition );   //差 - 看起來僅僅只是while / loop的部分之一。

9.10. 指針和引用表達式

總述

句點或箭頭前後不要有空格。指針/地址操作符()之後不能有空格。*, &

說明

下面是指針和引用表達式的正確使用範例:

x  =  * p ; 
p  =  &x ; 
x  =  r 。y ; 
x  =  r - > y ;

注意:

  • 在訪問成員時,句點或箭頭前後沒有空格。
  • 操作指針符  * 或  & 後沒有空格。

在聲明指針變量或參數時,星號與類型或變量名緊挨都可以:

//好,空格前置。
char  * c ; 
const  string  &str ;

//好,空格後置。
char *  c ; 
const  string & str ;
int  x , * y ;   //不允許 - 在多重聲明中不能使用&或* 
char  *  c ;   //差 -  *兩邊都有空格
const  string  & str ;   //差 - &兩邊都有空格。

在單個文件內要保持風格一致,所以,如果是修改現有文件,要遵照該文件的風格。

9.11. 布爾表達式

總述

如果一個布爾表達式超過  標準行寬,斷行方式要統一一下。

說明

下例中,邏輯與(&&)操作符總位於行尾:

if  (this_one_thing  >  this_other_thing  && 
    a_third_thing  ==  a_fourth_thing  && 
    yet_another  &&  last_one ) { 
  ... 
}

注意,上例的邏輯與(&&)操作符均位於行尾。這個格式在Google裏很常見,雖然把所有操作符放在開頭也可以。可以考慮額外插入圓括號,合理使用的話對增強可讀性是很有幫助的。此外,直接用符號形式的操作符,比如  && 和  ~,不要用詞語形式的  and 和  compl

9.12. 函數返回值

總述

在不要  return 表達式里加上非必須的圓括號。

說明

在只有寫   要加上括號的時候纔在   裏使用括號。x = exprreturn expr;

返回 結果;                   //返回很簡單,沒有圓括號。
//可以用圓括號把複雜表達式圈起來,改善可讀性。
return  (some_long_condition  && 
        another_condition );
回報 (價值);                 //畢竟您從從來不會寫var =(value); 
返回(結果);                 // return可不是函數!

9.13. 變量及數組初始化

總述

用  =,  () 狀語從句:  {} 均可。

說明

您可以用  =,  () 和  {},以下的例子都是正確的:

int  x  =  3 ; 
int  x (3 ); 
int  x { 3 }; 
字符串 名稱(“Some Name” ); 
字符串 名稱 =  “某個名稱” ; 
字符串 名稱{ “Some Name” };

請小心務必列表初始化  {...} 用  std::initializer_list 構造函數初始化出的類型。非空列表初始化就會優先調用  std::initializer_list,不過空列表初始化除外,後者原則上會調用默認構造函數。爲了強制禁用  std::initializer_list 構造函數,請改用括號。

矢量< INT >  v (100 , 1 );   //內容爲100個1的向量。
矢量< INT >  v { 100 , 1 };   //內容爲100和1的向量。

此外,列表初始化不允許整型類型的四捨五入,這可以用來避免一些類型上的編程失誤。

int  pi (3.14 );   //好 -  pi == 3. 
int  pi { 3.14 };   //編譯錯誤:縮略轉換。

9.14. 預處理指令

總述

預處理指令不要縮進,從行首開始。

說明

即使預處理指令位於縮進代碼塊中,指令也應從行首開始。

//好 - 指令從行首開始
  if  (lopsided_score ) { 
#if DISASTER_PENDING       //正確 - 從行首開始
    DropEverything (); 
#if NOTIFY                //非必要 - #後跟空格
    NotifyClient (); 
#endif 
#endif 
    BackToNormal (); 
  }
//差-指令縮進
  如果 (lopsided_score ) { 
    的#if DISASTER_PENDING   //差- “的#if”應該放在行開頭
    DropEverything (); 
    #endif                 //差 - “#endif”不要縮進
    BackToNormal (); 
  }

9.15. 類格式

總述

訪問控制塊的聲明依次序是  public:,  protected:,  private:,每個都縮進1個空格。

說明

類聲明(下面的代碼中缺少註釋,參考  類註釋)的基本格式如下:

class  MyClass  : public  OtherClass  { 
 public :      //注意有一個空格的縮進
  MyClass ();   //標準的兩空格縮進
  顯式 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_ ; 
};

注意事項:

  • 所有基類名應在80列限制下儘量與子類名放在同一行。
  • 關鍵詞  public:,  protected:,  private: 要縮進1個空格。
  • 除第一個關鍵詞(一般是  public)外,其他關鍵詞前要空一行。如果類比較小的話也可以不空。
  • 這些關鍵詞後不要保留空行。
  • public 放在最前面,然後是  protected,最後是  private
  • 聲明關於順序的規則請參考  聲明順序  一節。

9.16. 構造函數初始值列表

總述

構造函數初始化列表放在同一行或按四格縮進並排多行。

說明

下面兩種初始值列表方式都可以接受:

//如果所有變量能放在同一行:
MyClass :: MyClass (int  var ) : some_var_ (var ) { 
  DoSomething (); 
}

//如果不能放在同一行,
//必須置於冒號後,並縮進4個空格
MyClass :: MyClass (int  var )
    : some_var_ (var ), some_other_var_ (var  +  1 ) { 
  DoSomething (); 
}

//如果初始化列表需要置於多行,將每個成員放在單獨的一行
//並逐行對齊
MyClass :: MyClass (int  var )
    : some_var_ (var ),             // 4空格縮進
      some_other_var_ (var  +  1 ) {   //列隊
  DoSomething (); 
}

//右大括號}可以和左大括號{放在同一行
//如果這樣做合適的話
MyClass :: MyClass (int  var )
    : some_var_ (var ) {}

9.17. 命名空間格式化

總述

命名空間內容不縮進。

說明

命名空間  不要增加額外的縮進層次,例如:

命名空間 {

void  foo () {   //正確。命名空間內沒有額外的縮進。
  ... 
}

}   //命名空間

不要在命名空間內縮進:

命名空間 {

  //錯,縮進多餘了。
  void  foo () { 
    ... 
  }

}   //命名空間

聲明嵌套命名空間時,每個命名空間都獨立成行。

namespace  foo  { 
namespace  bar  {

9.19. 水平留白

總述

水平留白的使用根據在代碼中的位置決定。永遠不要在行尾添加沒意義的留白。

說明

通用

void  f (bool  b ) {   //左大括號前總是有空格。
  ... 
int  i  =  0 ;   //分號前不加空格。
//列表初始化中大括號內的空格是可選的。
//如果加了空格,那麼兩邊都要加。
int  x []  =  {  0  }; 
int  x []  =  { 0 };

//繼承與初始列表中的冒號前後恆有空格。
class  Foo  : public  Bar  { 
 public :
  //對於單行函數的實現,在大括號內加上空格
  //然後是函數實現
  Foo (int  b ) : Bar (), baz_ (b ) {}   //大括號裏面是空的話,不加空格。
  void  Reset () {  baz_  =  0 ;  }   //用括號把大括號與實現分開。
  ...

添加冗餘的留白會給其他人編輯時造成額外負擔。因此,行尾不要留空格。如果確定一行代碼已經修改完畢,將多餘的空格去掉; 或者在專門清理空格時去掉(尤其是在沒有其他人在處理這件事的時候)。(Yang.Y注:現在大部分代碼編輯器稍加設置後,都支持自動刪除行首/行尾空格,如果不支持,考慮換一款編輯器或IDE)

循環和條件語句

if  (b ) {           // if條件語句和循環語句關鍵字後均有空格。
}  else  {           // else前後有空格。
} 
while  (test ) {}    //圓括號內部不緊鄰空格。
switch  (i ) { 
for  (int  i  =  0 ;  i  <  5 ;  ++ i ) { 
switch  ( i  ) {     //循環和條件語句的圓括號裏可以與空格緊鄰。
如果 ( 測試 ) {      //圓括號,但這很少見。總之要一致。
for  ( int i  =  0 ;  我 <  5 ;  ++ i  ) { 
for  ( ;  i  <  5  ;  ++ i ) {   //循環裏內; 後恆有空格,; 前可以加個空格。
switch  (i ) { 
  case  1 :         // switch case的冒號前無空格。
    ... 
  案例 2 : 打破;   //如果冒號有代碼,加個空格。

操作符

//賦值運算符前後總是有空格。
x  =  0 ;

//其它二元操作符也前後恆有空格,不過對於表達式的子式可以不加空格。
//圓括號內部沒有緊鄰空格。
v  =  w  *  x  +  y  /  z ; 
v  =  w * x  +  y / z ; 
v  =  w  *  (x  +  z );

//在參數和一元操作符之間不加空格。
X  =  - 5 ; 
++ x ; 
如果 (x  &&  !y )
  ...

模板和轉換

//尖括號(<和>)不與空格緊鄰,<前沒有空格,>和(之間也沒有
矢量< 字符串>  X ; 
ý  =  的static_cast < 炭*> (X );

//在類型與指針操作符之間留空格也可以,但要保持一致。
vector < char  *>  x ;

9.19. 垂直留白

總述

垂直留白越少越好。

說明

這不僅僅是規則而是原則問題了:不在僅萬不得已,不要使用空行。尤其是:兩個函數定義之間的空行不要超過2行,函數體首尾不要留空行,函數體中也不要隨意添加空行。

基本原則是:同一屏可以顯示的代碼越多,越容易理解程序的控制流程。當然,過密集的代碼塊和過於疏鬆的代碼塊同樣難看,這取決於你的判斷。但通常是垂直留白越少越好。

下面的規則可以讓加入的空行更有效:有點可讀性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章