最全面的C/C++編碼規範總結

liyuefeilong的專欄發表於2016-01-28

對於不同的程式語言來說,具體的編碼規範可以有很大的不同,但是其宗旨都是一致的,就是保證程式碼在高質量完成需求的同時具備良好的可讀性、可維護性。例如我們可以規定某個專案的C語言程式要遵循這樣的規定:變數的命名,標頭檔案的書寫和#include 等等。

下面是一些廣為採用的編碼規範:

  • 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已經存在的。

相關文章