編碼規范參考


編碼規范參考

 

========================================================

建議使用的方式:

  • C with class
  • 接口,而非繼承
  • 用模板實現泛型,以及用靜態多態來代替接口
  • 模板元編程
  • unique_ptr、shared_ptr
  • 移動語義、const、所有權以及生命周期

 

========================================================

來源 https://zhuanlan.zhihu.com/p/71782780

 

下面是一些廣為采用的編碼規范:

  • GNU Coding Standards

  • Guidelines for the Use of the C Language in Vehicle Based Software

  • C++ Coding Guidelines

  • SUN Code Conventions for Java

以下是一些介紹編碼、編碼規范的書籍:

  • C++編碼規范,陳世忠,人民郵電出版社,2002

  • 高質量程序設計指南:C++/C語言,林銳等,電子工業出版社,2003

注:以下只是根據課題組已有的經驗給出的總結,並非對所有場景均適用。

對於高質量的工程,一般會做到:

  1. 代碼簡潔精煉,美觀,可讀性好,高效率,高復用,可移植性好,高內聚,低耦合,沒有冗余,不符合這些原則,必須特別說明。

  2. 規范性,代碼有規可循。特殊排版、特殊語法、特殊指令,必須特別說明。

一、文件排版方面

1.包含頭文件

1.1 先系統頭文件,后用戶頭文件。

1.2 系統頭文件,穩定的目錄結構,應采用包含子路徑方式。

1.3 自定義頭文件,不穩定目錄結構,應在dsp中指定包含路徑。

1.4 系統頭文件應用:#include <xxx.h>

1.5 自定義同文件應用:#include "xxx.h"

1.6 只引用需要的頭文件。

2.h和cpp文件

2.1 頭文件命名為*.h,內聯文件命名為*.inl;C++文件命名為*.cpp

2.2 文件名用大小寫混合,或者小寫混合。例如DiyMainview.cppinfoview.cpp。不要用無意義的名稱:例如XImage.cppSView.cppxlog.cpp

2.3 頭文件除了特殊情況,應使用#ifdef控制塊。

2.4 頭文件#endif應采用行尾注釋。

2.5 頭文件,首先是包含代碼塊,其次是宏定義代碼塊,然后是全局變量,全局常量,類型定義,類定義,內聯部分。

2.6 CPP文件,包含指令,宏定義,全局變量,函數定義。

3.文件結構

3.1 文件應包含文件頭注釋和內容。

3.2 函數體類體之間原則上用2個空行,特殊情況下可用一個或者不需要空行。

4.空行

4.1 文件頭、控制塊,#include部分、宏定義部分、class部分、全局常量部分、全局變量部分、函數和函數之間,用兩個空行。

二、注釋方面

1.文件頭注釋

1.1 作者,文件名稱,文件說明,生成日期(可選)

2.函數注釋

2.1 關鍵函數必須寫上注釋,說明函數的用途。

2.2 特別函數參數,需要說明參數的目的,由誰負責釋放等等。

2.3 除了特別情況,注釋寫在代碼之前,不要放到代碼行之后。

2.4 對每個#else#endif給出行末注釋。

2.5 關鍵代碼注釋,包括但不限於:賦值,函數調用,表達式,分支等等。

2.6 善未實現完整的代碼,或者需要進一步優化的代碼,應加上 // TODO ...

2.7 調試的代碼,加上注釋 // only for DEBUG

2.8 需要引起關注的代碼,加上注釋 // NOTE ...

2.9 對於較大的代碼塊結尾,如for,while,do等,可加上 // end for|while|do

三、命名方面

1.原則

1.1 同一性:在編寫一個子模塊或派生類的時候,要遵循其基類或整體模塊的命名風格,保持命名風格在整個模塊中的同一性。

1.2 標識符組成:標識符采用英文單詞或其組合,應當直觀且可以拼讀,可望文知意,用詞應當准確,避免用拼音命名。

1.3 最小化長度 && 最大化信息量原則:在保持一個標識符意思明確的同時,應當盡量縮短其長度。

1.4 避免過於相似:不要出現僅靠大小寫區分的相似的標識符,例如"i""I""function""Function"等等。

1.5 避免在不同級別的作用域中重名:程序中不要出現名字完全相同的局部變量和全局變量,盡管兩者的作用域不同而不會發生語法錯誤,但容易使人誤解。

1.6 正確命名具有互斥意義的標識符:用正確的反義詞組命名具有互斥意義的標識符,如:"nMinValue" 和 "nMaxValue""GetName()" 和"SetName()" ….

1.7 避免名字中出現數字編號:盡量避免名字中出現數字編號,如Value1,Value2等,除非邏輯上的確需要編號。這是為了防止程序員偷懶,不肯為命名動腦筋而導致產生無意義的名字(因為用數字編號最省事)。

2.T,C,M,R類

2.1 T類表示簡單數據類型,不對資源擁有控制權,在析構過程中沒有釋放資源動作。

2.2 C表示從CBase繼承的類。該類不能從棧上定義變量,只能從堆上創建。

2.3 M表示接口類。

2.4 R是資源類,通常是系統固有類型。除了特殊情況,不應在開發代碼中出現R類型。

3.函數名

3.1 M類的函數名稱應采用HandleXXX命名,例如:HandleTimerEvent;不推薦采用java風格,例如 handleTimerEvent;除了標准c風格代碼,不推薦用下划線,例如,handle_event

3.2 Leave函數,用后綴L。

3.3 Leave函數,且進清除棧,用后綴LC。

3.4 Leave函數,且刪除對象,用后綴LD。

4.函數參數

4.1 函數參數用a作為前綴。

4.2 避免出現和匈牙利混合的命名規則如apBuffer名稱。用aBuffer即可。

4.3 函數參數比較多時,應考慮用結構代替。

4.4 如果不能避免函數參數比較多,應在排版上可考慮每個參數占用一行,參數名豎向對齊。

5.成員變量

5.1 成員變量用m最為前綴。

5.2 避免出現和匈牙利混合的命名規則如mpBuffer名稱。用mBuffer即可。

6.局部變量

6.1 循環變量和簡單變量采用簡單小寫字符串即可。例如,int i;

6.2 指針變量用p打頭,例如void* pBuffer;

7.全局變量

7.1 全局變量用g_最為前綴。

8.類名

8.1 類和對象名應是名詞。

8.2 實現行為的類成員函數名應是動詞。

8.3 類的存取和查詢成員函數名應是名詞或形容詞。

9.風格兼容性

9.1 對於移植的或者開源的代碼,可以沿用原有風格,不用C++的命名規范。

四、代碼風格方面

1.Tab和空格

1.1 每一行開始處的縮進只能用Tab,不能用空格,輸入內容之后統一用空格。除了最開始的縮進控制用Tab,其他部分為了對齊,需要使用空格進行縮進。這樣可以避免在不同的編輯器下顯示不對齊的情況。

1.2 在代碼行的結尾部分不能出現多余的空格。

1.3 不要在"::","->","."前后加空格。

1.4 不要在",",";"之前加空格。

2.類型定義和{

2.1 類,結構,枚舉,聯合:大括號另起一行

3.函數

3.1 函數體的{需要新起一行,在{之前不能有縮進。

3.2 除了特別情況,函數體內不能出現兩個空行。

3.3 除了特別情況,函數體內不能宏定義指令。

3.4 在一個函數體內,邏揖上密切相關的語句之間不加空行,其它地方應加空行分隔。

3.5 在頭文件定義的inline函數,函數之間可以不用空行,推薦用一個空行。

4.代碼塊

4.1 "if"、"for"、"while"、"do"、"try"、"catch" 等語句自占一行,執行語句不得緊跟其后。不論執行語句有多少都要加 "{ }" 。這樣可以防止書寫和修改代碼時出現失誤。

4.2 "if"、"for"、"while"、"do"、"try"、"catch" 的括號和表達式,括號可緊挨關鍵字,這樣強調的是表達式。

5.else

5.1 if語句如果有else語句,用 } else { 編寫為一行,不推薦用 3 行代碼的方式。

6.代碼行

6.1 一行代碼只做一件事情,如只定義一個變量,或只寫一條語句。這樣的代碼容易閱讀,並且方便於寫注釋。

6.2 多行變量定義,為了追求代碼排版美觀,可將變量豎向對齊。

6.3 代碼行最大長度宜控制在一定個字符以內,能在當前屏幕內全部可見為宜。

7.switch語句

7.1 case關鍵字應和switch對齊。

7.2 case子語句如果有變量,應用{}包含起來。

7.3 如果有並列的類似的簡單case語句,可考慮將case代碼塊寫為一行代碼。

7.4 簡單的case之間可不用空行,復雜的case之間應考慮用空行分割開。

7.5 case字語句的大括號另起一行,不要和case寫到一行。

7.6 為所有switch語句提供default分支。

7.7 若某個case不需要break一定要加注釋聲明。

8.循環

8.1 空循環可用 for( ;; ) 或者 while( 1 ) 或者 while( true )

9.類

9.1 類繼承應采用每個基類占據一行的方式。

9.2 單繼承可將基類放在類定義的同一行。如果用多行,則應用Tab縮進。

9.3 多繼承在基類比較多的情況下,應將基類分行,並采用Tab縮進對齊。

9.4 重載基類虛函數,應在該組虛函數前寫注釋 // implement XXX

9.5 友元聲明放到類的末尾。

10.宏

10.1 不要用分號結束宏定義。

10.2 函數宏的每個參數都要括起來。

10.3 不帶參數的宏函數也要定義成函數形式。

11.goto

11.1 盡量不要用goto。

五、類型

  1. 定義指針和引用時*&緊跟類型。
  2. 盡量避免使用浮點數,除非必須。
  3. typedef簡化程序中的復雜語法。
  4. 避免定義無名稱的類型。例如:typedef enum { EIdle, EActive } TState;
  5. 少用union,如果一定要用,則采用簡單數據類型成員。
  6. enum取代(一組相關的)常量。
  7. 不要使用魔鬼數字。
  8. 盡量用引用取代指針。
  9. 定義變量完成后立即初始化,勿等到使用時才進行。
  10. 如果有更優雅的解決方案,不要使用強制類型轉換。

六、表達式

  1. 避免在表達式中用賦值語句。
  2. 避免對浮點類型做等於或不等於判斷。
  3. 不能將枚舉類型進行運算后再賦給枚舉變量。
  4. 在循環過程中不要修改循環計數器。
  5. 檢測空指針,用 if( p )
  6. 檢測非空指針,用 if( ! p )

七、函數

1.引用

1.1 引用類型作為返回值:函數必須返回一個存在的對象。

1.2 引用類型作為參數:調用者必須傳遞一個存在的對象。

2.常量成員函數

2.1 表示該函數只讀取對象的內容,不會對對象進行修改。

3.返回值

3.1 除開void函數,構造函數,析構函數,其它函數必須要有返回值。

3.2 當函數返回引用或指針時,用文字描述其有效期。

4.內聯函數

4.1 內聯函數應將函數體放到類體外。

4.2 只有簡單的函數才有必要設計為內聯函數,復雜業務邏輯的函數不要這么做。

4.3 虛函數不要設計為內聯函數。

5.函數參數

5.1 只讀取該參數的內容,不對其內容做修改,用常量引用。

5.2 修改參數內容,或需要通過參數返回,用非常量應用。

5.3 簡單數據類型用傳值方式。

5.4 復雜數據類型用引用或指針方式。

八、類

1.構造函數

1.1 構造函數的初始化列表,應和類的順序一致。

1.2 初始化列表中的每個項,應獨占一行。

1.3 避免出現用一個成員初始化另一個成員。

1.4 構造函數應初始化所有成員,尤其是指針。

1.5 不要在構造函數和析構函數中拋出異常。

2.純虛函數

2.1 M類的虛函數應設計為純虛函數。

3.構造和析構函數

3.1 如果類可以繼承,則應將類析構函數設計為虛函數。

3.2 如果類不允許繼承,則應將類析構函數設計為非虛函數。

3.3 如果類不能被復制,則應將拷貝構造函數和賦值運算符設計為私有的。

3.4 如果為類設計了構造函數,則應有析構函數。

4.成員變量

4.1 盡量避免使用mutableVolatile

4.2 盡量避免使用公有成員變量。

5.成員函數

5.1 努力使類的接口少而完備。

5.2 盡量使用常成員函數代替非常成員函數,const函數

5.3 除非特別理由,絕不要重新定義(繼承來的)非虛函數。(這樣是覆蓋,基類的某些屬性無初始化)

6.繼承

6.1 繼承必須滿足IS-A的關系,HAS-A應采用包含。

6.2 虛函數不要采用默認參數。

6.3 除非特別需要,應避免設計大而全的虛函數,虛函數功能要單一。

6.4 除非特別需要,避免將基類強制轉換成派生類。

7.友元

7.1 盡量避免使用友元函數和友元類。

九、錯誤處理

  1. 申請內存用new操作符。
  2. 釋放內存用delete操作符。
  3. newdeletenew[]delete[]成對使用。
  4. 申請內存完成之后,要檢測指針是否申請成功,處理申請失敗的情況。
  5. 誰申請誰釋放。優先級:函數層面,類層面,模塊層面。
  6. 釋放內存完成后將指針賦空,避免出現野指針。
  7. 使用指針前進行判斷合法性,應考慮到為空的情況的處理。
  8. 使用數組時,應先判斷索引的有效性,處理無效的索引的情況。
  9. 代碼不能出現編譯警告。
  10. 使用錯誤傳遞的錯誤處理思想。
  11. 衛句風格:先處理所有可能發生錯誤的情況,再處理正常情況。
  12. 嵌套do-while(0)宏:目的是將一組語句變成一個語句,避免被其他if等中斷。

十、性能

  1. 使用前向聲明代替#include指令。Class M;
  2. 盡量用++i代替i++。即用前綴代替后綴運算。
  3. 盡量在for循環之前,先寫計算估值表達式。
  4. 盡量避免在循環體內部定義對象。
  5. 避免對象拷貝,尤其是代價很高的對象拷貝。
  6. 避免生成臨時對象,尤其是大的臨時對象。
  7. 注意大尺寸對象數組。
  8. 80-20原則。

十一、兼容性

  1. 遵守ANSI C和ISO C++國際標准。
  2. 確保類型轉換不會丟失信息。
  3. 注意雙字節字符的兼容性。
  4. 注意運算溢出問題。
  5. 不要假設類型的存儲尺寸。
  6. 不要假設表達式的運算順序。
  7. 不要假設函數參數的計算順序。
  8. 不要假設不同源文件中靜態或全局變量的初始化順序。
  9. 不要依賴編譯器基於實現、未明確或未定義的功能。
  10. 將所有#include的文件名視為大小寫敏感。
  11. 避免使用全局變量、靜態變量、函數靜態變量、類靜態變量。在使用靜態庫,動態庫,多線程環境時,會導致兼容性問題。
  12. 不要重新實現標准庫函數,如STL已經存在的。

 

 

========================================================

來源 https://zhuanlan.zhihu.com/p/20326454

綜述

C++ 是一門十分復雜並且威力強大的語言,使用這門語言的時候我們應該有所節制,絕對的自由意味着混亂。

我十分清楚每個人對怎么編寫代碼都有自己的偏好。這里定下的規范,某些地方可能會跟個人原來熟悉的習慣相違背,並引起不滿。但多人協作的時候,需要有一定規范。定下一些規范,當大家面對某些情況,有所分歧的時候,容易達成共識。另外通過一定規范,加強代碼的一致性,從團隊中某人的代碼切換到另一個人的代碼,會更為自然,讓別人可以讀懂你的代碼是很重要的。

通常面對某種情況,會有兩種或更多種做法,我們就挑選其中一種,共同遵守。這並不表示另一種做法就是錯的,只是僅僅不同。

這里規范是死的,現實是多變的,當你覺得某些規范,對你需要解決問題反而有很大限制,可以違反,但要有理由,而不僅僅是借口。那到底是否在尋找借口,並沒有很明確的判斷標准。這就如同不能規定少於多少根頭發為禿頭,但當我們看到某個人的時候,自然能夠判斷他是否是禿頭。同樣,當我們碰到具體情況的時候,自然能夠判斷是否在尋找借口。

本規范編寫過程中,大量參考了《Google C++ 編程規范》,Google那份規范十分好,建議大家對比着看。

------------------------------------------

1 格式

1.1 每行代碼不多於 80 個字符

從前的電腦終端,每行只可以顯示 80 個字符。現在有更大更寬的顯示屏,很多人會認為這條規則已經沒有必要。但我們有充分的理由:

  • 版本控制軟件,或者編碼過程中,經常需要在同一顯示屏幕上,左右並排對比新舊兩個文件。80 個字符的限制,使得兩個文件都不會折行,對比起來更清晰。
  • 當代碼超過 3 層嵌套,代碼行就很容易超過 80 個字符。這條規則防止我們嵌套太多層級,層級嵌套太深會使得代碼難以讀懂。

規則總會有例外。比如當你有些代碼行,是 82 個字符,假如我們強制規定少於80字符,人為將一行容易讀的代碼拆分成兩行代碼,就太不人性化了。我們可以適當超過這個限制。

1.2 使用空格(Space),而不是制表符(Tab)來縮進,每次縮進4個字符

代碼編輯器,基本都可以設置將Tab轉為空格,請打開這個設置。

制表符在每個軟件中的顯示,都會有所不同。有些軟件中每個Tab縮進8個字符,有些軟件每個Tab縮進4個字符,隨着個人的設置不同而不同。只使用空格來縮進,保證團隊中每個人,看同一份代碼,格式不會亂掉。

1.3 指針符號*,引用符號& 的位置,寫在靠近類型的地方

CCNode* p = CCNode::create(); // (1) CCNode *p = CCNode::create(); // (2) 

也就說,上面兩種寫法。寫成第(1)種。

我知道這個規定有很大的爭議。指針符號到底靠近類型,還是靠近變量,這爭論一直沒有停過。其實兩種寫法都沒有什么大問題,關鍵是統一。經考慮,感覺第1種寫法更統一更合理。理由:

  • 在類中連續寫多個變量,通常會用 Tab 將變量對齊。( Tab 會轉化成空格)。比如

CCNode* _a;
CCNode _b;
int _c;

當星號靠近類型而不是變量。_a, _b, _c 等變量會很自然對齊。

而當星號靠近變量,如果不手動多按空格微調,會寫成。

CCNode *_a;
CCNode _b;
int _c;

  • 指針符號靠近類型,語法上更加統一。比如

const char* getTableName();
static_cast<CCLayer*>(node); 

反對第一種寫法的理由通常是:

  • 假如某人連續定義多個變量,就會出錯。

int* a, b, c;

上面寫法本身就有問題。應該每行定義一個變量, 並初始化。

int* a = nullptr;
int* b = nullptr;
int* c = nullptr;

  • Xcode中,默認的語法提示,指針符號靠近變量,我再修改成靠近類型,比較麻煩。

這個有點道理。但我們也不能十分依賴工具。可以使用clang_format等美化工具去輔助調整代碼。

1.4 花括號位置

采用Allman風格,if, for, while,namespace, 命名空間等等的花括號,另起一行。例子

for (auto i = 0; i < 100; i++)
{
    printf("%d\n", i);
}

這條規定,很可能又引起爭議。很多人采用 K&R 風格,將上面代碼寫成

for (auto i = 0; i < 100; i++) {
    printf("%d\n", i);
}

K&R風格在書籍印刷上會節省紙張。但在實際的代碼中顯得過於密集。Allman風格會更加清晰易讀。當然,這理由帶有很多主觀因素。

1.5 if, for, while等語句就算只有一行,也強制使用花括號

永遠不要省略花括號,不要寫成:

if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
    goto fail;

需要寫成:

if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
{
    goto fail;
}

省略花括號,以后修改代碼,或者代碼合並的時候,容易直接多寫一行。如

if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
    goto fail;
    goto fail;

就會引起錯誤。

------------------------------------------

 

2 命名約定

2.1 使用英文單詞,不能夾着拼音

這條規則強制執行,不能有例外。

2.2 總體上采用駱駝命名法

單詞與單詞之間,使用大小寫相隔的方式分開,中間不包含下划線。比如

TimerManager  // (1) playMusic     // (2) 

其中(1)為大寫的駱駝命名法,(2)為小寫的駱駝命名法。

不要使用

timer_manager
play_music

這種小寫加下划線的方式在 boost 庫,C++ 標准庫中,用得很普遍。

接下來分別描述具體的命名方式。

2.3 名字不要加類型前綴

有些代碼庫,會在變量名字前面加上類型前綴。比如 b表示 bool, i 表示 int , arr 表示數組, sz 表示字符串等等。他們會命名為

bool          bEmpty;
const char*   szName;
Array         arrTeachers;

我們不提倡這種做法。變量名字應該關注用途,而不是它的類型。上面名字應該修改為

bool        isEmpty;
const char* name;
Array       teachers;

注意,我們將 bool 類型添加上is。isEmpty, isOK, isDoorOpened,等等,讀起來就是一個詢問句。

2.4 類型命名

類型命名采用大寫的駱駝命名法,每個單詞以大寫字母開頭,不包含下划線。比如

GameObject
TextureSheet

類型的名字,應該帶有描述性,是名詞,而不要是動詞。盡量避開Data, Info, Manager 這類的比較模糊的字眼。(但我知道有時也真的避免不了,看着辦。)

所有的類型,class, struct, typedef, enum, 都使用相同的約定。例如

classUrlTable
struct UrlTableProperties
typedef hash_map<UrlTableProperties*, std::string> PropertiesMap;
enum UrlTableError

2.5 變量命名

2.5.1 普通變量名字

變量名字采用小寫的駱駝命名法。比如

std::string tableName;
CCRect      shapeBounds;

變量的名字,假如作用域越長,就越要描述詳細。作用域越短,適當簡短一點。比如

for (auto& name : _studentNames)
{
    std::cout << name << std::endl;
}

for (size_t i = 0; i < arraySize; i++)
{
    array[i] = 1.0;
}

名字清晰,並且盡可能簡短。

2.5.2 類成員變量

成員變量,訪問權限只分成兩級,private 和 public,不要用 protected。 私有的成員變量,前面加下划線。比如:

classImage
{
public:
    .....

private:
    size_t    _width;
    size_t    _height;
}

public 的成員變量,通常會出現在 C 風格的 struct 中,前面不用加下划線。比如:

struct Color4f
{
    float    red;
    float    green;
    float    blue;
    float    alpha;
}

2.5.3 靜態變量

類中盡量不要出現靜態變量。類中的靜態變量不用加任何前綴。文件中的靜態變量統一加s_前綴,並盡可能的詳細命名。比如

static ColorTransformStack s_colorTransformStack;    // 對 static ColorTransformStack s_stack;                  // 錯(太簡略) 

2.5.4 全局變量

不要使用全局變量。真的沒有辦法,加上前綴 g_,並盡可能的詳細命名。比如

Document  g_currentDocument;

2.6 函數命名

變量名字采用小寫的駱駝命名法。比如

playMusic
getSize
isEmpty

函數名字。整體上,應該是個動詞,或者是形容詞(返回bool的函數),但不要是名詞。

teacherNames();        // 錯(這個是總體是名詞) getTeacherNames();     // 對 

無論是全局函數,靜態函數,私有的成員函數,都不強制加前綴。但有時靜態函數,可以適當加s_前綴。

類的成員函數,假如類名已經出現了某種信息,就不用重復寫了。比如

classUserQueue
{
public:
    size_t getQueueSize();    // 錯(類名已經為Queue了,                               // 這里再命名為getQueueSize就無意義)     size_t getSize();         // 對 }

2.7 命名空間

命令空間的名字,使用小寫加下划線的形式,比如

namespace lua_wrapper;

使用小寫加下划線,而不要使用駱駝命名法。可以方便跟類型名字區分開來。比如

lua_wrapper::getField();  // getField是命令空間lua_wrapper的函數 LuaWrapper::getField();   // getField是類型LuaWrapper的靜態函數 

2.8 宏命名

不建議使用宏,但真的需要使用。宏的名字,全部大寫,中間加下划線相連接。這樣可以讓宏更顯眼一些。比如

#define PI_ROUNDED 3.0 CLOVER_TEST
MAX
MIN

頭文件出現的防御宏定義,也全部大寫,比如:

#ifndef __COCOS2D_FLASDK_H__ #define __COCOS2D_FLASDK_H__ 
....

#endif 

不要寫成這樣:

#ifndef __cocos2d_flashsdk_h__ #define __cocos2d_flashsdk_h__ 
....

#endif 

2.9 枚舉命名

盡量使用 0x11 風格 enum,例如:

enum classColorType : uint8_t
{
    Black,
    While,
    Red,
}

枚舉里面的數值,全部采用大寫的駱駝命名法。使用的時候,就為 ColorType::Black

有些時候,需要使用0x11之前的enum風格,這種情況下,每個枚舉值,都需要帶上類型信息,用下划線分割。比如

enum HttpResult
{
    HttpResult_OK     = 0,
    HttpResult_Error  = 1,
    HttpResult_Cancel = 2,
}

2.10 純 C 風格的接口

假如我們需要結構里面的內存布局精確可控,有可能需要編寫一些純C風格的結構和接口。這個時候,接口前面應該帶有模塊或者結構的名字,中間用下划線分割。比如

struct HSBColor
{
    float h;
    float s;
    float b;
};

struct RGBColor
{
    float r;
    float g;
    float b;
}

RGBColor color_hsbToRgb(HSBColor hsb);
HSBColor color_rgbToHsb(RGBColor rgb);

這里,color 就是模塊的名字。這里的模塊,充當 C++ 中命名空間的作用。

struct Path
{
    ....
}

Path* Path_new();
void  Path_destrory(Path* path);
void  Path_moveTo(Path* path, float x, float y);
void  Path_lineTo(Path* path, float x, float y);

這里,接口中Path出現的是類的名字。

2.11 代碼文件,路徑命名

代碼文件的名字,應該反應出此代碼單元的作用。

比如 Point.h, Point.cpp,實現了class Point;

當 class Point,的名字修改成,Point2d, 代碼文件名字,就應該修改成 Point2d.h, Point2d.cpp。代碼文件名字,跟類型名字一樣,采用大寫的駱駝命名法。

路徑名字,對於於模塊的名字。跟上一章的命名規范一樣,采用小寫加下划線的形式。比如

ui/home/HomeLayer.h
ui/battle/BattleCell.h
support/geo/Point.h
support/easy_lua/Call.h

路徑以及代碼文件名,不能出現空格,中文,不能夾着拼音。假如隨着代碼的修改,引起模塊名,類型名字的變化,應該同時修改文件名跟路徑名。

2.12 命名避免帶有個人標簽

比如,不要將某個模塊名字為

HJCPoint
hjc/Label.h

hjc為團隊某人名字的縮寫。

項目歸全體成員所有,任何人都有權利跟義務整理修改工程代碼。當某樣東西打上個人標記,就傾向將其作為私有。其他人就會覺得那代碼亂不關自己事情,自己就不情願別人來動自己東西。

當然了,文件開始注釋可以出現創建者的名字,信息。只是類型,模塊,函數名字,等容易在工程中散開的東西不提倡。個人項目可以忽略這條。

再強調一下,任何人都有權利跟義務整理修改他人代碼,只要你覺得你修改得合理,但不要自作聰明。我知道有些程序員,會覺得他人修改自己代碼,就是入侵自己領土。

2.13 例外

有些時候,我們需要自己寫的庫跟C++的標准庫結合。這時候可以采用跟C++標准庫相類似的風格。比如

classMyArray
{
public:
    typedef const char* const_iteator;
    ...

    const char* begin() const;
    const char* rbegin() const;
}

----------------

3 代碼文件

3.1 #define 保護

所有的頭文件,都應該使用#define來防止頭文件被重復包含。命名的格式為

__<模塊>_<文件名>_H__

很多時候,模塊名字都跟命名空間對應。比如

#ifndef __GEO_POINT_H__ #define __GEO_POINT_H__ 
namespace geo
{
    classPoint
    {
        .....
    };
}

#endif 

並且,#define宏,的名字全部都為大寫。不要出現大小寫混雜的形式。

3.2 #include 的順序

C++代碼使用#include來引入其它的模塊的頭文件。盡可能,按照模塊的穩定性順序來排列#include的順序。按照穩定性從高到低排列。

比如

#include <map>#include <vector>#include <boost/noncopyable.hpp>#include "cocos2d.h"#include "json.h"#include "FlaSDK.h"#include "support/TimeUtils.h"#include "Test.h"

上面例子中。#include的順序,分別是C++標准庫,boost庫,第三方庫,我們自己寫的跟工程無關的庫,工程中比較基礎的庫,應用層面的文件。

但有一個例外,就是 .cpp中,對應的.h文件放在第一位。比如geo模塊中的, Point.h 跟 Point.cpp文件,Point.cpp中的包含

#include "geo/Point.h"#include <cmath>

這里,將 #include "geo/Point.h",放到第一位,之后按照上述原則來排列#include順序。理由下一條規范來描述。

3.3 盡可能減少頭文件的依賴

代碼文件中,每出現一次#include包含, 就會多一層依賴。比如,有A,B類型,各自有對應的.h文件和.cpp文件。

當A.cpp包含了A.h, A.cpp就依賴了A.h,我們表示為

A.cpp -> A.h

這樣,當A.h被修改的時候,A.cpp就需要重修編譯。 假設

B.cpp -> B.h
B.h   -> A.h

這表示,B.cpp 包含了B.h, B.h包含了A.h, 這個時候。B.cpp雖然沒有直接包含A.h, 但也間接依賴於A.h。當A.h修改了,B.cpp也需要重修編譯。

當在頭文件中,出現不必要的包含,就會生成不必要的依賴,引起連鎖反應,使得編譯時間大大被拉長。

使用前置聲明,而不是直接#include,可以顯著地減少依賴數量。實踐方法:

3.3.1 頭文件第一位包含

比如寫類A,有文件 A.h, 和A.cpp 那么在A.cpp中,將A.h的包含寫在第一位。在A.cpp中寫成

// 前面沒有別的頭文件包含 #include "A.h"#include <string>#include .......

.... 包含其它頭文件

之后可以嘗試在 A.h 中去掉多余的頭文件。當A.cpp可以順利編譯通過的時候,A.h包含的頭文件就是過多或者剛剛好的。而不會是包含不夠的。

3.3.2 前置聲明

首先,只在頭文件中使用引用或者指針,而不是使用值的,可以前置聲明。而不是直接包含它的頭文件。 比如

classTest : public Base
{
public:
    void funA(const A& a);
    void funB(const B* b);
    void funC(const space::C& c);

private:
    D   _d;
};

這里,我牽涉到幾個其它類,Base, A, B, space::C(C 在命名空間space里面), D。Base和D需要知道值,A, B, space::C只是引用和指針。所以Base, C的頭文件需要包含。A, B,space::C只需要前置聲明。

#include "Base.h"#include "D.h"
namespace space
{
    classC;
}

classA;
classB;
classTest : public Base
{
public:
    void funA(const A& a);
    void funB(const B* b);
    void funC(const space::C& c);

private:
    D   _d;
};

注意命名空間里面的寫法。

3.3.3 impl 手法

就是類里面包含實現類的指針。在cpp里面實現。

3.3.4 盡可能將代碼拆分成相對獨立的,粒度小的單元,放到不同的文件中

簡單說,就是不要將所有東西都塞在一起。這樣的代碼組積相對清晰。頭文件包含也相對較少。但現實中,或多或少會違反。

比如,工程用到一些常量字符串(或者消息定義,或者enum值,有多個變種)。一個似乎清晰的結構,是將字符串都放到同一個頭文件中。不過這樣一來,這個字符串文件,就幾乎會被所有項目文件包含。當以后新加一個字符串時候,就算只加一行,工程幾乎被全部編譯。

更好的做法,是按照字符串的用途來分拆開。

又比如,有些支持庫。有時貪圖方便,不注意的,就會寫一個 GlobalUtils.h 之類的頭文件,包含所有支持庫,因為這樣可以不關心到底應該包含哪個,反正包含GlobalUtils.h就行,這樣多省事。不過這樣一來,需要加一個支持的函數,比如就只是角度轉弧度的小函數,也會發生連鎖編譯。

更好的做法,是根據需要來包含必要的文件。就算你麻煩一點,寫10行#include的代碼,都比之后修改一行代碼,就編譯上10多分鍾要好。

3.4 小結

減少編譯時間,這點很重要。再啰嗦一下

  • 要減少頭文件重復包含,需要團隊的人所有人達成共識,認識到這是不好的。很多人對這問題認識不夠,會被當成小題大作。
  • 不要貪方便。直接包含一個大的頭文件,短期是很方便,長期會有麻煩。

3.5 #include中的頭文件,盡量使用全路徑,或者相對路徑

路徑的起始點,為工程文件代碼文件的根目錄。

比如

#include "ui/home/HomeLayer.h"#include "ui/home/HomeCell.h"#include "support/MathUtils.h"

不要直接包含

#include "HomeLayer.h"#include "HomeCell.h"#include "MathUtils.h"

這樣可以防止頭文件重名,比如一個第三方庫文件有可能就叫 MathUtils.h。

並且移植到其它平台,配置起來會更容易。比如上述例子,在安卓平台上,就需要配置包含路徑

<Project_Root>/ui/home/
<Project_Root>/support/

也可以使用相對路徑。比如

#include "../MathUtil.h"#include "./home/HomeCell.h"

這樣做,還有個好處。就是只用一個簡單腳本,或者一些簡單工具。就可以分析出頭文件的包含關系圖,然后就很容易看出循環依賴。

--------------------

4 作用域

作用域,表示某段代碼或者數據的生效范圍。作用域越大,修改代碼時候影響區域也就越大,原則上,作用域越小越好。

4.1 全局變量

禁止使用全局變量。全局變量在項目的任何地方都可以訪問。兩個看起來沒有關系的函數,一旦訪問了全局變量,就會產生無形的依賴。使用全局變量,基本上都是怕麻煩,貪圖方便。比如

funA -> funB -> funC -> funD

上圖表示調用順序。當funD需要用到funA中的某個數據。正確的方式,是將數據一層層往下傳遞。但因為這樣做,需要修改幾個地方,修改的人怕麻煩,直接定義出全局變量。這樣做,當然是可以快速fix bug。但funA跟funD就引入無形的依賴,從接口處看不出來。

單件可以看做全局變量的變種。最優先的方式,應該將數據從接口中傳遞,其次封裝單件,再次使用函數操作靜態數據,最糟糕就是使用全局變量。

若真需要使用全局變量。變量使用g_開頭。

4.2 類的成員變量

類的成員變量,只能夠是private或者public, 不要設置成protected。protected的數據看似安全,實際只是一種錯覺。

數據只能通過接口來修改訪問,不要直接訪問。這樣的話,在接口中設置個斷點就可以調試知道什么時候數據被修改。另外改變類的內部數據表示,也可以維持接口的不變,而不影響全局。

絕大多數情況,數據都應該設置成私有private, 變量加 _前綴。比如

classData
{
private:
    const uint8_t*  _bytes;
    size_t          _size;
}

公有的數據,通常出現在C風格的結構中,或者一些數據比較簡單,並很常用的類,public數據不要加前綴。

classPoint
{
public:
    Point(float x_, float y_) : x(x_), y(y_)
    {
    }

    .....

    float x;
    float y;
}

注意,我們在構造函數,使用 x_ 的方式表示傳入的參數,防止跟 x 來重名。

4.3 局部變量

局部變量盡可能使它的作用范圍最小。換句話說,就是需要使用的時候才定義,而不要在函數開始就全部定義。

從前C語言有個約束,需要將用到的全部變量都定義在函數最前面。之后這個習慣也被傳到C++的代碼當中。但這種習慣是很不好的。

  • 在函數最前面定義變量,變量就在整個函數都可見,作用域越大,就越容易被誤修改。
  • C++ 中,定義類型的變量,需要調用構造函數,跟釋放函數。很多時候函數中途就退出了,這時候調用構造函數和釋放函數,就顯得浪費。
  • 變量在最開始的時候,很難給變量一個合理的初始值,很難的話,也就很容易忘記。

我們的結論是,局部變量真正需要使用的時候才定義,一行定義一個變量,並且一開始就給它一個合適的初始值。

int i;
i = f();     // 錯,初始化和定義分離 int j = g(); // 對,定義時候給出始值 

4.4 命名空間

C++中,盡量不要出現全局函數,應該放入某個命名空間當中。命名空間將全局的作用域細分,可有效防止全局作用域的名字沖突。

比如

namespace json
{
    classValue
    {
        ....
    }
}

namespace splite
{
    classValue
    {
        ...
    }
}

兩個命名空間都出現了Value類。外部訪問時候,使用 json::Value, splite::Value來區分。

4.5 文件作用域

假如,某個函數,或者類型,只在某個.cpp中使用,請將函數或者類放入匿名命名空間。來防止文件中的函數導出。比如

// fileA.cpp namespace
{
    void doSomething()
    {
        ....
    }
}

上述例子,doSomething這個函數,放入了匿名空間。因此,此函數限制在fileA.cpp中使用。另外的文件定義相同名字的函數,也不會造成沖突。

另外傳統C的做法,是在 doSomething 前面加 static, 比如

// fileB.cpp static void doSomething()
{
    ...
}

doSomething也限制到文件fileB.cpp中。

同理,只在文件中出現的類型,也放到匿名空間中。比如

// sqlite/Value.cpp namespace sqlite
{
    namespace
    {
        classRecord
        {
            ....
        }
    }
}

上述例子,匿名空間嵌套到sqlite空間中。這樣Record這個結構只可以在sqlite/Value.cpp中使用,就算是同屬於空間sqlite的文件,也不知道 Record 的存在。

4.6 頭文件不要出現 using namespace ....

頭文件,很可能被多個文件包含。當某個頭文件出現了 using namespace ... 的字樣,所有包含這個頭文件的文件,都簡直看到此命令空間的全部內容,就有可能引起沖突。比如

// Test.h #include <string>using namespace std;

classTest
{
public:
    Test(const string& name);
};

這個時候,只要包含了Test.h, 就都看到std的所有內容。正確的做法,是頭文件中,將命令空間寫全。將 string, 寫成 std::string, 這里不要偷懶。

----------------

5 類

面向對象編程中,類是基本的代碼單元。本節列舉了在寫一個類的時候,需要注意的事情。

5.1 讓類的接口盡可能小

設計類的接口時,不要想着接口以后可能有用就先加上,而應該想着接口現在沒有必要,就直接去掉。這里的接口,你可以當成類的成員函數。添加接口是很容易的,但是修改,去掉接口會會影響較大。

接口小,不單指成員函數的數量少,也指函數的作用域盡可能小。

比如,

classTest
{
public:
    void funA();
    void funB();
    void funC();
    void funD();
};

假如,funD 其實是可以使用 funA, funB, funC 來實現的。這個時候,funD,就不應該放到Test里面。可以將funD抽取出來。funD 只是一個封裝函數,而不是最核心的。

void Test_funD(Test* test);

編寫類的函數時候,一些輔助函數,優先采用 Test_funD 這樣的方式,將其放到.cpp中,使用匿名空間保護起來,外界就就不用知道此函數的存在,那些都只是實現細節。

當不能抽取獨立於類的輔助函數,先將函數,變成private, 有必要再慢慢將其提出到public。 不要覺得這函數可能有用,一下子就寫上一堆共有接口。

再強調一次,如無必要,不要加接口。

從作用域大小,來看

  • 獨立於類的函數,比類的成員函數要好
  • 私有函數,比共有函數要好
  • 非虛函數,比虛函數要好

5.2 聲明順序

類的成員函數或者成員變量,按照使用的重要程度,從高到低來排列。

比如,使用類的時候,用戶更關注函數,而不是數據,所以成員函數應該放到成員變量之前。 再比如,使用類的時候,用戶更關注共有函數,而不是私有函數,所以public,應該放在private前面。

具體規范

  • 按照 public, protected, private 的順序分塊。那一塊沒有,就直接忽略。

每一塊中,按照下面順序排列

  • typedef,enum,struct,class 定義的嵌套類型
  • 常量
  • 構造函數
  • 析構函數
  • 成員函數,含靜態成員函數
  • 數據成員,含靜態數據成員

.cpp 文件中,函數的實現盡可能給聲明次序一致。

5.3 繼承

優先使用組合,而不是繼承。

繼承主要用於兩種場合:實現繼承,子類繼承了父類的實現代碼。接口繼承,子類僅僅繼承父類的方法名稱。

我們不提倡實現繼承,實現繼承的代碼分散在子類跟父親當中,理解起來變得很困難。通常實現繼承都可以采用組合來替代。

規則:

  • 繼承應該都是 public
  • 假如父類有虛函數,父類的析構函數為 virtual
  • 假如子類覆寫了父類的虛函數,應該顯式寫上 override

比如

// swf/Definition.h classDefinition
{
public:
    virtual ~Definition()   {}
    virtual void parse(const uint8_t* bytes, size_t len) = 0;
};

// swf/ShapeDefinition.h classShapeDefinition : public Definition
{
public:
    ShapeDefinition()   {}
    virtual void parse(const uint8_t* bytes, size_t len) override;

private:
    Shape   _shape;
};

 

Definition* p = new ShapeDefinition();
....
delete p;

上面的例子,使用父類的指針指向子類,假如父類的析構函數不為virtual, 就只會調用父類的Definition的釋放函數,引起子類獨有的數據不能釋放。所有需要加上virtual。

另外子類覆寫的虛函數寫上,override的時候,當父類修改了虛函數的名字,就會編譯錯誤。從而防止,父類修改了虛函數接口,而忘記修改子類相應虛函數接口的情況。

--------------------

6 函數

6.1 編寫短小的函數

函數盡可能的短小,凝聚,功能單一。

只要某段代碼,可以用某句話來描述,盡可能將這代碼抽取出來,作為獨立的函數,就算那代碼只有一行。最典型的就是C++中的max, 實現只有一句話。

template <typename T>
inline T max(T a, T b)
{
    return a > b ? a : b;
}
  • 將一段代碼抽取出來,作為一個整體,一個抽象,就不用糾結在細節之中。
  • 將一個長函數,切割成多個短小的函數。每個函數中使用的局部變量,作用域也會變小。
  • 短小的函數,更容易復用,從一個文件搬到另一個文件也會更容易。
  • 短小的函數,因為內存局部性,運行起來通常會更快。
  • 短小的函數,也容易閱讀,調試。

6.2 函數的參數可能少,原則上不超過5個

人腦短時記憶的數字是很有限的,大約可以記憶7個數字。有些人多些,有些人少些。我們這里取最少值,就是5個參數。

參數的個數,太多,就很容易混亂,記不住參數的意義。

同時參數的個數太多,很可能是因為這個函數做的事情有點多了。

可以通過很多手段來減少參數的個數。比如將函數分解,分解成多個短小的函數。或者將幾個經常一起的參數,封裝成一個類或者結構。比如,設計一個繪畫貝塞爾曲線的接口

void drawQuadBeizer(float startX,   float startY,
                    float controlX, float controlY,
                    float endX,     float endY);

這樣的接口,就不夠

void drawQuadBeizer(const Point& start,
                    const Point& control,
                    const Point& end);

簡潔易用。

當然,每個規則都會有例外。比如設置一個矩陣的數值,二維矩陣本來就需要6個數字來表示,設置接口自然需要6個參數。

6.3 函數參數順序

參數順序,按照傳入參數,傳出參數,的順序排列。不要使用可傳入可傳出的參數。

bool loadFile(const std::string& filePath, ErrorCode* code);  // 對 bool loadFile(ErrorCode* code, const std::string& filePath);  // 錯 

保持統一的順序,使得他人容易記憶。

6.4 函數的傳出參數,使用指針,而不要使用引用

比如

bool loadFile(const std::string& filePath, ErrorCode* code);  // 對 bool loadfile(const std::string& filePath, ErrorCode& code);  // 錯 

因為當使用引用的時候,使用函數的時候會變成

ErrorCode code;
if (loadFile(filePath, code))
{
    ...
}

而使用指針,調用的時候,會是

ErrorCode code;
if (loadFile(filePath, &code))
{
    ...
}

這樣從,&code的方式可以很明顯的區分,傳入,傳出參數。試比較

doFun(arg0, arg1, arg2);    // 錯 doFun(arg0, &arg1, &arg2);  // 對 

6.5 不建議使用函數的缺省參數

我們經常會通過查看現有的代碼來了解如何使用函數的接口。缺省參數使得某些參數難以從調用方就完全清楚,需要去查看函數的接口,也就是完全了解某個接口,需要查看兩個地方。

另外,缺省參數那個數值,其實是實現的一部分,寫在頭文件是不適當的。

缺省參數,其實可以通過將一個函數拆分成兩個函數。實現放到.cpp中。

-------------------

7 其它

7.1 const的使用

我們建議,盡可能的多使用const。

C++中,const是個很重要的關鍵字,應用了const之后,就不可以隨便改變變量的數值了,不小心改變了編譯器會報錯,就容易找到錯誤的地方。只要你覺得有不變的地方,就用const來修飾吧。比如:

  • 想求圓的周長,需要用到Pi, Pi不會變的,加const,const double Pi = 3.1415926;
  • 需要在函數中傳引用,只讀,不會變的,前面加const;
  • 函數有個返回值,返回值是個引用,只讀,不會變的,前面加const;
  • 類中有個private數據,外界要以函數方式讀取,不會變的,加const,這個時候const就是加在函數定義末尾。

const的位置:

const int* name;  // 對(這樣寫,可讀性更好) int const* name;  // 錯 

7.2 不要注釋代碼,代碼不使用就直接刪掉

有些人不習慣使用版本控制工具,某段代碼不再使用了,他們會注釋掉代碼,而不是直接刪除掉。他們的理由是,這段代碼現在沒有用,可能以后會有用,我注釋了,以后真的再用的時候,就不用再寫了。

不要這樣做。

注釋掉的代碼,放在源文件里面,會將正常的代碼搞混亂。有個破窗理論,說假如一個窗戶破了,不去管它,路人就會傾向敲爛其它的窗戶。同樣,假如你看到代碼某個地方亂了,會覺得再搞的更亂也沒有關系,就會越來越亂。

而在現代的版本控制工具下,只要寫好提交記錄,找回從前的代碼是很容易的。

 

================ End

 

 

下面是一些廣為采用的編碼規范:

  • GNU Coding Standards
  • Guidelines for the Use of the C Language in Vehicle Based Software
  • C++ Coding Guidelines
  • SUN Code Conventions for Java

以下是一些介紹編碼、編碼規范的書籍:

  • C++編碼規范,陳世忠,人民郵電出版社,2002
  • 高質量程序設計指南:C++/C語言,林銳等,電子工業出版社,2003

注:以下只是根據課題組已有的經驗給出的總結,並非對所有場景均適用。

對於高質量的工程,一般會做到:

  1. 代碼簡潔精煉,美觀,可讀性好,高效率,高復用,可移植性好,高內聚,低耦合,沒有冗余,不符合這些原則,必須特別說明。
  2. 規范性,代碼有規可循。特殊排版、特殊語法、特殊指令,必須特別說明。

一、文件排版方面

1.包含頭文件

1.1 先系統頭文件,后用戶頭文件。

1.2 系統頭文件,穩定的目錄結構,應采用包含子路徑方式。

1.3 自定義頭文件,不穩定目錄結構,應在dsp中指定包含路徑。

1.4 系統頭文件應用:#include <xxx.h>

1.5 自定義同文件應用:#include "xxx.h"

1.6 只引用需要的頭文件。

2.h和cpp文件

2.1 頭文件命名為*.h,內聯文件命名為*.inl;C++文件命名為*.cpp

2.2 文件名用大小寫混合,或者小寫混合。例如DiyMainview.cppinfoview.cpp。不要用無意義的名稱:例如XImage.cppSView.cppxlog.cpp

2.3 頭文件除了特殊情況,應使用#ifdef控制塊。

2.4 頭文件#endif應采用行尾注釋。

2.5 頭文件,首先是包含代碼塊,其次是宏定義代碼塊,然后是全局變量,全局常量,類型定義,類定義,內聯部分。

2.6 CPP文件,包含指令,宏定義,全局變量,函數定義。

3.文件結構

3.1 文件應包含文件頭注釋和內容。

3.2 函數體類體之間原則上用2個空行,特殊情況下可用一個或者不需要空行。

4.空行

4.1 文件頭、控制塊,#include部分、宏定義部分、class部分、全局常量部分、全局變量部分、函數和函數之間,用兩個空行。

二、注釋方面

1.文件頭注釋

1.1 作者,文件名稱,文件說明,生成日期(可選)

2.函數注釋

2.1 關鍵函數必須寫上注釋,說明函數的用途。

2.2 特別函數參數,需要說明參數的目的,由誰負責釋放等等。

2.3 除了特別情況,注釋寫在代碼之前,不要放到代碼行之后。

2.4 對每個#else#endif給出行末注釋。

2.5 關鍵代碼注釋,包括但不限於:賦值,函數調用,表達式,分支等等。

2.6 善未實現完整的代碼,或者需要進一步優化的代碼,應加上 // TODO ...

2.7 調試的代碼,加上注釋 // only for DEBUG

2.8 需要引起關注的代碼,加上注釋 // NOTE ...

2.9 對於較大的代碼塊結尾,如for,while,do等,可加上 // end for|while|do

三、命名方面

1.原則

1.1 同一性:在編寫一個子模塊或派生類的時候,要遵循其基類或整體模塊的命名風格,保持命名風格在整個模塊中的同一性。

1.2 標識符組成:標識符采用英文單詞或其組合,應當直觀且可以拼讀,可望文知意,用詞應當准確,避免用拼音命名。

1.3 最小化長度 && 最大化信息量原則:在保持一個標識符意思明確的同時,應當盡量縮短其長度。

1.4 避免過於相似:不要出現僅靠大小寫區分的相似的標識符,例如"i""I""function""Function"等等。

1.5 避免在不同級別的作用域中重名:程序中不要出現名字完全相同的局部變量和全局變量,盡管兩者的作用域不同而不會發生語法錯誤,但容易使人誤解。

1.6 正確命名具有互斥意義的標識符:用正確的反義詞組命名具有互斥意義的標識符,如:"nMinValue" 和 "nMaxValue""GetName()" 和"SetName()" ….

1.7 避免名字中出現數字編號:盡量避免名字中出現數字編號,如Value1,Value2等,除非邏輯上的確需要編號。這是為了防止程序員偷懶,不肯為命名動腦筋而導致產生無意義的名字(因為用數字編號最省事)。

2.T,C,M,R類

2.1 T類表示簡單數據類型,不對資源擁有控制權,在析構過程中沒有釋放資源動作。

2.2 C表示從CBase繼承的類。該類不能從棧上定義變量,只能從堆上創建。

2.3 M表示接口類。

2.4 R是資源類,通常是系統固有類型。除了特殊情況,不應在開發代碼中出現R類型。

3.函數名

3.1 M類的函數名稱應采用HandleXXX命名,例如:HandleTimerEvent;不推薦采用java風格,例如 handleTimerEvent;除了標准c風格代碼,不推薦用下划線,例如,handle_event

3.2 Leave函數,用后綴L。

3.3 Leave函數,且進清除棧,用后綴LC。

3.4 Leave函數,且刪除對象,用后綴LD。

4.函數參數

4.1 函數參數用a作為前綴。

4.2 避免出現和匈牙利混合的命名規則如apBuffer名稱。用aBuffer即可。

4.3 函數參數比較多時,應考慮用結構代替。

4.4 如果不能避免函數參數比較多,應在排版上可考慮每個參數占用一行,參數名豎向對齊。

5.成員變量

5.1 成員變量用m最為前綴。

5.2 避免出現和匈牙利混合的命名規則如mpBuffer名稱。用mBuffer即可。

6.局部變量

6.1 循環變量和簡單變量采用簡單小寫字符串即可。例如,int i;

6.2 指針變量用p打頭,例如void* pBuffer;

7.全局變量

7.1 全局變量用g_最為前綴。

8.類名

8.1 類和對象名應是名詞。

8.2 實現行為的類成員函數名應是動詞。

8.3 類的存取和查詢成員函數名應是名詞或形容詞。

9.風格兼容性

9.1 對於移植的或者開源的代碼,可以沿用原有風格,不用C++的命名規范。

四、代碼風格方面

1.Tab和空格

1.1 每一行開始處的縮進只能用Tab,不能用空格,輸入內容之后統一用空格。除了最開始的縮進控制用Tab,其他部分為了對齊,需要使用空格進行縮進。這樣可以避免在不同的編輯器下顯示不對齊的情況。

1.2 在代碼行的結尾部分不能出現多余的空格。

1.3 不要在"::","->","."前后加空格。

1.4 不要在",",";"之前加空格。

2.類型定義和{

2.1 類,結構,枚舉,聯合:大括號另起一行

3.函數

3.1 函數體的{需要新起一行,在{之前不能有縮進。

3.2 除了特別情況,函數體內不能出現兩個空行。

3.3 除了特別情況,函數體內不能宏定義指令。

3.4 在一個函數體內,邏揖上密切相關的語句之間不加空行,其它地方應加空行分隔。

3.5 在頭文件定義的inline函數,函數之間可以不用空行,推薦用一個空行。

4.代碼塊

4.1 "if"、"for"、"while"、"do"、"try"、"catch" 等語句自占一行,執行語句不得緊跟其后。不論執行語句有多少都要加 "{ }" 。這樣可以防止書寫和修改代碼時出現失誤。

4.2 "if"、"for"、"while"、"do"、"try"、"catch" 的括號和表達式,括號可緊挨關鍵字,這樣強調的是表達式。

5.else

5.1 if語句如果有else語句,用 } else { 編寫為一行,不推薦用 3 行代碼的方式。

6.代碼行

6.1 一行代碼只做一件事情,如只定義一個變量,或只寫一條語句。這樣的代碼容易閱讀,並且方便於寫注釋。

6.2 多行變量定義,為了追求代碼排版美觀,可將變量豎向對齊。

6.3 代碼行最大長度宜控制在一定個字符以內,能在當前屏幕內全部可見為宜。

7.switch語句

7.1 case關鍵字應和switch對齊。

7.2 case子語句如果有變量,應用{}包含起來。

7.3 如果有並列的類似的簡單case語句,可考慮將case代碼塊寫為一行代碼。

7.4 簡單的case之間可不用空行,復雜的case之間應考慮用空行分割開。

7.5 case字語句的大括號另起一行,不要和case寫到一行。

7.6 為所有switch語句提供default分支。

7.7 若某個case不需要break一定要加注釋聲明。

8.循環

8.1 空循環可用 for( ;; ) 或者 while( 1 ) 或者 while( true )

9.類

9.1 類繼承應采用每個基類占據一行的方式。

9.2 單繼承可將基類放在類定義的同一行。如果用多行,則應用Tab縮進。

9.3 多繼承在基類比較多的情況下,應將基類分行,並采用Tab縮進對齊。

9.4 重載基類虛函數,應在該組虛函數前寫注釋 // implement XXX

9.5 友元聲明放到類的末尾。

10.宏

10.1 不要用分號結束宏定義。

10.2 函數宏的每個參數都要括起來。

10.3 不帶參數的宏函數也要定義成函數形式。

11.goto

11.1 盡量不要用goto。

五、類型

  1. 定義指針和引用時*&緊跟類型。
  2. 盡量避免使用浮點數,除非必須。
  3. typedef簡化程序中的復雜語法。
  4. 避免定義無名稱的類型。例如:typedef enum { EIdle, EActive } TState;
  5. 少用union,如果一定要用,則采用簡單數據類型成員。
  6. enum取代(一組相關的)常量。
  7. 不要使用魔鬼數字。
  8. 盡量用引用取代指針。
  9. 定義變量完成后立即初始化,勿等到使用時才進行。
  10. 如果有更優雅的解決方案,不要使用強制類型轉換。

六、表達式

  1. 避免在表達式中用賦值語句。
  2. 避免對浮點類型做等於或不等於判斷。
  3. 不能將枚舉類型進行運算后再賦給枚舉變量。
  4. 在循環過程中不要修改循環計數器。
  5. 檢測空指針,用 if( p )
  6. 檢測非空指針,用 if( ! p )

七、函數

1.引用

1.1 引用類型作為返回值:函數必須返回一個存在的對象。

1.2 引用類型作為參數:調用者必須傳遞一個存在的對象。

2.常量成員函數

2.1 表示該函數只讀取對象的內容,不會對對象進行修改。

3.返回值

3.1 除開void函數,構造函數,析構函數,其它函數必須要有返回值。

3.2 當函數返回引用或指針時,用文字描述其有效期。

4.內聯函數

4.1 內聯函數應將函數體放到類體外。

4.2 只有簡單的函數才有必要設計為內聯函數,復雜業務邏輯的函數不要這么做。

4.3 虛函數不要設計為內聯函數。

5.函數參數

5.1 只讀取該參數的內容,不對其內容做修改,用常量引用。

5.2 修改參數內容,或需要通過參數返回,用非常量應用。

5.3 簡單數據類型用傳值方式。

5.4 復雜數據類型用引用或指針方式。

八、類

1.構造函數

1.1 構造函數的初始化列表,應和類的順序一致。

1.2 初始化列表中的每個項,應獨占一行。

1.3 避免出現用一個成員初始化另一個成員。

1.4 構造函數應初始化所有成員,尤其是指針。

1.5 不要在構造函數和析構函數中拋出異常。

2.純虛函數

2.1 M類的虛函數應設計為純虛函數。

3.構造和析構函數

3.1 如果類可以繼承,則應將類析構函數設計為虛函數。

3.2 如果類不允許繼承,則應將類析構函數設計為非虛函數。

3.3 如果類不能被復制,則應將拷貝構造函數和賦值運算符設計為私有的。

3.4 如果為類設計了構造函數,則應有析構函數。

4.成員變量

4.1 盡量避免使用mutableVolatile

4.2 盡量避免使用公有成員變量。

5.成員函數

5.1 努力使類的接口少而完備。

5.2 盡量使用常成員函數代替非常成員函數,const函數

5.3 除非特別理由,絕不要重新定義(繼承來的)非虛函數。(這樣是覆蓋,基類的某些屬性無初始化)

6.繼承

6.1 繼承必須滿足IS-A的關系,HAS-A應采用包含。

6.2 虛函數不要采用默認參數。

6.3 除非特別需要,應避免設計大而全的虛函數,虛函數功能要單一。

6.4 除非特別需要,避免將基類強制轉換成派生類。

7.友元

7.1 盡量避免使用友元函數和友元類。

九、錯誤處理

  1. 申請內存用new操作符。
  2. 釋放內存用delete操作符。
  3. newdeletenew[]delete[]成對使用。
  4. 申請內存完成之后,要檢測指針是否申請成功,處理申請失敗的情況。
  5. 誰申請誰釋放。優先級:函數層面,類層面,模塊層面。
  6. 釋放內存完成后將指針賦空,避免出現野指針。
  7. 使用指針前進行判斷合法性,應考慮到為空的情況的處理。
  8. 使用數組時,應先判斷索引的有效性,處理無效的索引的情況。
  9. 代碼不能出現編譯警告。
  10. 使用錯誤傳遞的錯誤處理思想。
  11. 衛句風格:先處理所有可能發生錯誤的情況,再處理正常情況。
  12. 嵌套do-while(0)宏:目的是將一組語句變成一個語句,避免被其他if等中斷。

十、性能

  1. 使用前向聲明代替#include指令。Class M;
  2. 盡量用++i代替i++。即用前綴代替后綴運算。
  3. 盡量在for循環之前,先寫計算估值表達式。
  4. 盡量避免在循環體內部定義對象。
  5. 避免對象拷貝,尤其是代價很高的對象拷貝。
  6. 避免生成臨時對象,尤其是大的臨時對象。
  7. 注意大尺寸對象數組。
  8. 80-20原則。

十一、兼容性

  1. 遵守ANSI C和ISO C++國際標准。
  2. 確保類型轉換不會丟失信息。
  3. 注意雙字節字符的兼容性。
  4. 注意運算溢出問題。
  5. 不要假設類型的存儲尺寸。
  6. 不要假設表達式的運算順序。
  7. 不要假設函數參數的計算順序。
  8. 不要假設不同源文件中靜態或全局變量的初始化順序。
  9. 不要依賴編譯器基於實現、未明確或未定義的功能。
  10. 將所有#include的文件名視為大小寫敏感。
  11. 避免使用全局變量、靜態變量、函數靜態變量、類靜態變量。在使用靜態庫,動態庫,多線程環境時,會導致兼容性問題。
  12. 不要重新實現標准庫函數,如STL已經存在的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM