天天看點

Google 的 C++ 代碼規範

        Google C++ 編碼規範很早就已經公開了,李開複也在其微網誌上公開分享:”我認為這是地球上最好的一份 C++ 程式設計規範,沒有之一,建議廣大國内外IT研究使用。“

        Google C++ Style Guide是一份不錯的C++編碼指南,下面是一張比較全面的說明圖,可以在短時間内快速掌握規範的重點内容。不過規範畢竟是人定的,記得活學活用。

  1. 保持一緻也非常重要,如果你在一個檔案中新加的代碼和原有代碼風格相去甚遠的話,這就破壞了檔案本身的整體美觀也影響閱讀,是以要盡量避免。
  2. 一些條目往往有例外,比如下面這些,是以本圖不能代替文檔,有時間還是把PDF認真閱讀一遍吧。

    異常在測試架構中确實很好用

    RTTI在某些單元測試中非常有用

    在記錄日志時可以使用流

        操作符重載 不提倡使用,有些STL 算法确實需要重載operator==時可以這麼做。

注:原圖較大,在新标簽頁中打開或儲存到本地打開更清晰

Google 的 C++ 代碼規範

頭檔案

  函數參數順序

  C/C++函數參數分為輸入參數和輸出參數兩種,有時輸入參數也會輸出(注:值被修改時)。輸入參數一般傳值或常數引用(const references),輸出參數戒輸入/輸出參數為非常數指針(non-const pointers)。對參數排序時,将所有輸入參數置于輸出參數之前。不要僅僅因為是新添加的參數,就将其置于最後,而應該依然置于輸出參數之前。這一點并不是必須遵循的規則,輸入/輸出兩用參數(通常是類/結構體變量)混在其中,會使得規則難以遵守。

  個人感受:這條規則相當重要,自己寫代碼的時候可能沒有太大感覺,但是在閱讀别人代碼的時候感覺特别明顯。如果代碼按照這種規範來寫,從某種角度來說,這段代碼具有“自注釋”的功能,那麼在看代碼的時候就會比較輕松。Doom3的代碼規範中提到,“Use ‘const’ as much as possible”,也是同樣的意義。當然,const除了閱讀友善以外,還有個很重要的就是防止編碼錯誤,一旦在程式中修改const變量,編譯器就會報錯,這樣就減少了人工出錯了可能性,這點尤為重要!

  包含檔案的名稱及次序

  将包含次序标準化可增強可讀性、避免隐藏依賴(hidden dependencies,注:隐藏依賴主要是指包含的檔案編譯),次序如下:C 庫、C++庫、其他庫的.h、項目内的.h。

  項目内頭檔案應按照項目源代碼目錄樹結構排列,并且避免使用UNIX檔案路徑.(目前目錄)和..(父目錄)。 

  舉例來說,google-awesome-project/src/foo/internal/fooserver.cc 的包含次序如下: 

#include "foo/public/fooserver.h" // 優先位置 
  
  #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"      

  注意,對應的頭檔案一定要先包含,這樣避免隐藏依賴,隐藏依賴的問題不懂的可以去Google,網上有很多資料。另外,《C++程式設計思想》中提到的包含次序正好相反,從特殊到一般,但是有一點和Google代碼規範是一樣的,那就是對應的頭檔案是第一個包含。對于隐藏依賴的問題,以前隻是習慣性的把對應的頭檔案放第一個,沒有想過為什麼,現在學習了……

作用域

  全局變量

  class 類型的全局變量是被禁止的,内建類型的全局變量是允許的,當然多線程代碼中非常數全局變量也是被禁止的。永遠不要使用函數傳回值初始化全局變量。

  不幸的是,全局變量的構造函數、析構函數以及初始化操作的調用順序隻是被部分規定,每次生成有可能會有變化,進而導緻難以發現bug。是以,禁止使用class類型的全局變量(包括STL的string,vector等),因為它們的初始化順序可能會導緻出現問題。内建類型和由内建類型構成的沒有構造函數的結構體可以使用,如果你一定要使用class類型的全局變量,請使用單件模式。

C++類

  構造函數的職責

  構造函數中隻進行那些沒有實際意義的初始化,可能的話,使用Init()方法集中初始化為有意義(non-trivial)的資料。

  個人感受:這種做法可以從一開始就避免一些bug的出現,或更容易解決一些bug。構造函數+Init()函數初始化的方式與隻用構造函數的方法相比,對計算機來說他們是沒有差別的,但是人是會犯錯的,這一條代碼規範在某種程度上避免了一些人為錯誤,這個在開發中特别重要。

  拷貝構造函數

  僅在代碼中需要拷貝一個類的對象的時候使用拷貝構造函數,不需要拷貝時使用DISALLOW_COPY_AND_ASSIGN這個宏(關于這個宏的内容,可以在網上搜到,我這裡就不寫了)。C++中對象的隐式拷貝是導緻很多性能問題和bugs的根源。拷貝構造函數降低了代碼可讀性,相比按引用傳遞,跟蹤按值傳遞的對象更加困難,對象修改的地方變得難以捉摸。

  個人感受:和上一項的目的類似,為了避免人為錯誤!拷貝構造函數本來是為了友善程式員程式設計了,但是卻有可能成為一個坑,為了避免這類問題,不需要拷貝時使用DISALLOW_COPY_AND_ASSIGN,這樣在需要調用拷貝構造函數的時候就會報錯,減少了人為出錯的可能性。C#和Java在這方面就做得比較好,雖然性能上不如C++,但是人為出錯的機率減少了很多。當然,使用一定的代碼規範,可以在一定程度上減少C++的坑。

  繼承

  雖然C++的繼承很好用,但是在實際開發中,盡量多用組合少用繼承,不懂的去看GoF的《Design Patterns》。

  但重定義派生的虛函數時,在派生類中明确聲明其為virtual。這一條是為了為了閱讀友善,雖然從文法的角度來說,在基類中聲明了virtual,子類可以不用再聲明該函數為virtual,但這樣一來閱讀代碼的人需要檢索類的所有祖先以确定該函數是否為虛函數o(╯□╰)o。

  多重繼承

  雖然允許,但是隻能一個基類有實作,其他基類是接口,這樣一來和JAVA一樣了。這些東西在C#和JAVA中都進行了改進,直接從文法上解決問題。C++的靈活性過高,也是個麻煩的問題,隻能通過代碼規範填坑。

  接口

  虛基類必須以Interface為字尾,友善閱讀。閱讀友善。

  重載操作符

  除少數特定情況外,不要重載操作符!!!“==”和“=”的操作Euqals和CopyFrom函數代替,這樣更直覺,也不容易出錯。

  個人感受:看到這一條,我有點驚訝,在學習C++的時候,說重載操作符有神馬神馬好處,為什麼現在又說不要重載操作符呢?仔細看了他的文檔,确實說的有道理,導緻可能出現的bug見其具體文檔。在實際應用中,由于C++的坑實在太多了,不得不把這種“好用”的東西幹掉,因為出了bug又找不到,是一件很O疼的事情。

  聲明次序

  1)typedefs和enums;

  2)常量;

  3)構造函數;

  4)析構函數;

  5)成員函數,含靜态成員函數;

  6)資料成員,含靜态資料成員。

  宏 DISALLOW_COPY_AND_ASSIGN 置于private:塊之後,作為類的最後部分。

其他 C++ 特性

  引用參數

  函數形參表中,所有的引用必須的const!

  個人感受:這麼做是為了防止引用引起的誤解,因為引用在文法上是值,卻有指針的意義。雖然引用比較好用,但是犧牲其某些方面的特性,換來軟體管理方面的便利,還是很值得了。

  預設參數

  禁止使用函數預設參數!

  個人感受:看到這一點的時候覺得有點因噎廢食了,其實預設參數感覺還是蠻好用的。當然從另外一個角度來說,要使用C++就不要怕這種小麻煩,如果因為使用這些特性造成了找不到的bug,那會損失更多時間。

  異常

  不要使用 C++異常。

  這一點我沒有看懂,也許是因為它的異常機制沒有C#和Java那麼完善吧……畢竟在C#和Java裡面異常還是很好用的東東。

  流

  除了記錄日志,不要使用流,使用printf之類的代替。

  這一條其實是有一些争議的,當然大多數人認為代碼一緻性比較重要,是以選擇printf,具體的可以看原文文檔。

  const 的使用

  在任何可以的情況下都要使用const。

  這條規則贊一個,Doom3的代碼規範裡也提到了這一條。這麼做有兩個好處,一個是防止程式出錯,因為修改了const類型的變量會報錯;另一個就是友善閱讀,使代碼“自注釋”。雖然這麼做也有壞處,當然,總體來說利大于弊。

命名約定

  1、總體規則:不要随意縮寫,如果說 ChangeLocalValue 寫作ChgLocVal還有情可原的話,把ModifyPlayerName寫作MdfPlyNm就太過分了,除函數名可适當為動詞外,其他命名盡量使用清晰易懂的名詞; 

  2、宏、枚舉等使用全部大寫+下劃線; 

  3、變量(含類、結構體成員變量)、檔案、命名空間、存取函數等使用全部小寫+下劃線,類成員變量以下劃線結尾,全局變量以g_開頭; 

  4、普通函數、類型(含類與結構體、枚舉類型)、常量等使用大小寫混合,不含下劃線; 

  使用這套命名約定,可以使代碼具有一定程度的“自注釋”功能,友善他人閱讀,也友善自己以後修改。當然3、4兩點也可以使用其他的命名約定,隻要團隊統一即可。

格式 

  1、行寬原則上不超過80列,把22寸的顯示屏都占完,怎麼也說不過去;

  2、盡量不使用非ASCII字元,如果使用的話,參考 UTF-8 格式(尤其是 UNIX/Linux 下,Windows 下可以考慮寬字元),盡量不将字元串常量耦合到代碼中,比如獨立出資源檔案,返不僅僅是風格問題了;

  3、UNIX/Linux下無條件使用空格,MSVC的話使用 Tab 也無可厚非; (我沒用過Linux,不懂為什麼在Linux下無條件使用空格)

  4、函數參數、邏輯條件、初始化清單:要麼所有參數和函數名放在同一行,要麼所有參數并排分行;

  5、除函數定義的左大括号可以置于行首外,包括函數/類/結極體/枚舉聲明、各種語句的左大括号置于行尾,所有右大括号獨立成行;

  6、./->操作符前後丌留白格,*/&不要前後都留,一個就可,靠左靠右依各人喜好;

  7、預處理指令/命名空間不使用額外縮進,類/結構體/枚舉/函數/語句使用縮進;

  8、初始化用=還是()依個人喜好,統一就好;

  9、return不要加();

  10、水準/垂直留白不要濫用,怎麼易讀怎麼來。 

寫在最後

  總的來說,這套代碼規範還是相當不錯的,既有防止錯誤使用C++的某些特性而導緻bugs的規範,又有代碼書寫的相關規範使其便于閱讀,建議搞C++的童鞋都看一看。當然,具體的團隊應該會有具體的代碼規範,代碼風格方面大家可能會有一些差別;不使用C++某些特性(比如不使用C++異常,禁止使用函數預設參數)方面,應該按照具體情況進行折中處理,而不應該生搬硬套代碼規範;但是“不将字元串常量耦合到代碼中”這種規範,是大家必須遵守的。

三種程式設計命名規範(匈牙利命名法、駝峰式命名法、帕斯卡命名法)

三種流行的命名法則

目前業界共有四種命名法則:駝峰命名法、匈牙利命名法、帕斯卡命名法 和 下劃線命名法,其中前三種是較為流行的命名法。

1 . 匈牙利命名:

開頭字母用變量類型的縮寫,其餘部分用變量的英文或英文的縮寫,且每個單詞的第一個字母都大寫。

示例: 

int iMyAge;           // i 是 int 類型的縮寫。
char cMyName[10];     // c 是 char 類型的縮寫。 
float fManHeight;     // f 是 float 類型的縮寫。      

匈牙利命名廣泛應用于象 Microsoft Windows 這樣的環境中, Windows 程式設計中用到的變量(還包括宏)的命名規則都是匈牙利命名法,這種命名技術是由一位能幹的 Microsoft 程式員查爾斯- 西蒙尼(Charles Simonyi) 提出的

匈牙利命名法 通過在變量名前面加上相應的小寫字母的符号辨別作為字首,辨別出變量的作用域,類型等這些符号可以多個同時使用,順序是先 m_(成員變量), 再指針,再簡單資料類型,再其它 。

例如:m_lpszStr, 表示指向一個以0字元結尾的字元串的長指針成員變量 

匈牙利命名法關鍵是:辨別符的名字以一個或者多個小寫字母開頭作為字首;字首之後的是首字母大寫的一個單詞或多個單詞組合,該單詞要指明變量的用途 

匈牙利命名法中常用的小寫字母的字首:

屬性 + 類型 + 描述。屬性一般是 小寫字母 + _ :

g_ : 全局變量

m_ : 類成員變量

s_ : 靜态變量

c_ : 常量

類 型 前 綴 類  型
a    數組 (Array)  
b    布爾值 (Boolean)  
by    位元組 (Byte)  
c    有符号字元 (Char),用 c 開頭 cCount
cb    無符号字元 (Char Byte,沒有多少人用)  
cr    顔色參考值 (ColorRef)  
d    double 用d開頭 dDeta
f    float 用f開頭 fAvg 
cx,cy    坐标差(長度 ShortInt)  
w    Word,unsigned int(WORD) 用w開頭 wCount
dw    Double Word,unsigned long int(DWORD) 用dw開頭 dwBroad
fn    函數  
h    Handle(句柄)  
i    整型 int ,用 i 開頭 iCount
n    short int  短整型  用 n 開頭 nStepCount
l    Long Int   長整型  用 l 開頭 lSum
lp    Long Pointer  
m_    類的成員  
np    Near Pointer  
p    Pointer  
s    字元串類型,用s開頭 sFileName
sz

   以null做結尾的字元串型 (String with Zero End),

   用\0結尾的字元串 用sz開頭 szFileName

2 . 駝峰式命名法(小駝峰式 命名法):

小駝峰法(camel方法):第一個單詞 以小寫字母開始;第二個單詞的首字母大寫,或從第二個單詞開始,後面的每一個單詞的首字母都采用大寫字母,即 小駝峰式命名法: 第一個單詞首字母小寫,後面其他單詞首字母大寫。

變量 一般用 小駝峰法辨別。

ex: 

int myAge; 

char myName[10]; 

float manHeight;

小駝峰式 命名規則(第一個單詞首字母小寫,後面所有單詞的首字母都大寫):firstName, camelCase

大駝峰式 命名規則(所有單詞的首字母都大寫):FirstName, CamelCase

下面是分别用 駱駝式命名法 和 下劃線法命名 的同一個函數:

printEmployeePaychecks();    駱駝式命名法 ----  函數名中的每一個邏輯斷點都有一個大寫字母來标記

print_employee_paychecks();下劃線法         ----  函數名中的每一個邏輯斷點都有一個下劃線來标記。

3 . 帕斯卡命名法(大駝峰式 命名法):

帕斯卡命名法(pascal方法)又叫 大駝峰式命名法。相比小駝峰法,大駝峰法把第一個單詞的首字母也大寫了, 每個單詞的第一個字母都大寫。即 駱駝命名法是首字母小寫,而帕斯卡命名法是首字母大寫

(pascal方法)常用于 類名,函數名,屬性,命名空間。

大駝峰法(Upper Camel Case) ex: 

int MyAge; 

char MyName[10]; 

float ManHeight ;

public class DataBaseUser

4. 下劃線 命名規則

下劃線法是随着 C語言 的出現流行起來的,在 UNIX/LIUNX 這樣的環境,以及 GNU 代碼中使用非常普遍 

Google 的 C++ 代碼規範

5. 命名規則 小結:

MyData 就是一個帕斯卡命名的示例 。

而 myData 是一個駱駝命名法,它第一個單詞的第一個字母小寫,後面的單詞首字母大寫,看起來像一個駱駝 。

而 iMyData 是一個匈牙利命名法,它的小寫的i說明了它的型态,後面的和帕斯卡命名相同,訓示了該變量的用途。

通常每種語言都有自己的 Coding Style,比如 C/C++ 和 python 是下劃線,java 和 go 是駝峰。

是以,對于要使用哪種命名法可以根據個人的代碼編寫風格,也是可使用不同的命名規範混合使用。

如:駱駝 + 下劃線 (int temperature_Sensor;)

Google 的 C++ 代碼規範
上一篇: 樹形DP選講
下一篇: Joda-Time簡介

繼續閱讀