零、前言
筆者最近在看開源代碼,看到代碼格式各自參差不齊,感覺像是各家各有所長。因此打算寫一篇關於C/C++代碼規范文章,請各位參考,並踐踏批評。
一、文件排版
1. 包含頭文件
• 先系統頭文件,后用戶頭文件。
• 系統頭文件,穩定的目錄結構,應采用包含子路徑方式。
• 自定義頭文件,不穩定目錄結構,應在dsp中指定包含路徑。
• 系統頭文件應用:#include <xxx.h>
• 自定義同文件應用:#include "xxx.h"
• 只引用需要的頭文件。
2. h和cpp文件
• 頭文件命名為.h,內聯文件命名為.inl;C++文件命名為*.cpp
• 文件名用大小寫混合,或者小寫混合。例如DiyMainview.cpp
,infoview.cpp
。不要用無意義的名稱:例如XImage.cpp
;SView.cpp
;xlog.cpp
;
• 頭文件除了特殊情況,應使用#ifdef
控制塊。
• 頭文件#endif
應采用行尾注釋。
• 頭文件,首先是包含代碼塊,其次是宏定義代碼塊,然后是全局變量,全局常量,類型定義,類定義,內聯部分。
• CPP文件,包含指令,宏定義,全局變量,函數定義。
3. 文件結構
• 文件應包含文件頭注釋和內容。
• 函數體類體之間原則上用2個空行,特殊情況下可用一個或者不需要空行。
4. 空行
• 文件頭、控制塊,#include
部分、宏定義部分、class
部分、全局常量部分、全局變量部分、函數和函數之間,用兩個空行。
二、注釋
1. 文件頭注釋
• 作者,文件名稱,文件說明,生成日期(可選)
2. 函數注釋
• 關鍵函數必須寫上注釋,說明函數的用途。
• 特別函數參數,需要說明參數的目的,由誰負責釋放等等。
• 除了特別情況,注釋寫在代碼之前,不要放到代碼行之后。
• 對每個#else
或#endif
給出行末注釋。
• 關鍵代碼注釋,包括但不限於:賦值,函數調用,表達式,分支等等。
• 善未實現完整的代碼,或者需要進一步優化的代碼,應加上 // TODO …
• 調試的代碼,加上注釋 // only for DEBUG
• 需要引起關注的代碼,加上注釋 // NOTE …
• 對於較大的代碼塊結尾,如for,while,do
等,可加上 // end for|while|do
三、命名
1. 原則
• 同一性:在編寫一個子模塊或派生類的時候,要遵循其基類或整體模塊的命名風格,保持命名風格在整個模塊中的同一性。
• 標識符組成:標識符采用英文單詞或其組合,應當直觀且可以拼讀,可望文知意,用詞應當准確,避免用拼音命名。
• 最小化長度 && 最大化信息量原則:在保持一個標識符意思明確的同時,應當盡量縮短其長度。
• 避免過於相似:不要出現僅靠大小寫區分的相似的標識符,例如"i"與"I"
,"function"
與"Function"
等等。
• 避免在不同級別的作用域中重名:程序中不要出現名字完全相同的局部變量和全局變量,盡管兩者的作用域不同而不會發生語法錯誤,但容易使人誤解。
• 正確命名具有互斥意義的標識符:用正確的反義詞組命名具有互斥意義的標識符,如:"nMinValue"
和"nMaxValue"
,"GetName()"
和"SetName()"
….
• 避免名字中出現數字編號:盡量避免名字中出現數字編號,如Value1,Value2等,除非邏輯上的確需要編號。這是為了防止程序員偷懶,不肯為命名動腦筋而導致產生無意義的名字(因為用數字編號最省事)。
2. T,C,M,R類
• T類表示簡單數據類型,不對資源擁有控制權,在析構過程中沒有釋放資源動作。
• C表示從CBase繼承的類。該類不能從棧上定義變量,只能從堆上創建。
• M表示接口類。
• R是資源類,通常是系統固有類型。除了特殊情況,不應在開發代碼中出現R類型。
3. 函數名
• M類的函數名稱應采用HandleXXX
命名,例如:HandleTimerEvent;
不推薦采用java風格,例如handleTimerEvent;
除了標准c風格代碼,不推薦用下划線,例如,handle_event
。
• Leave函數,用后綴L。
• Leave函數,且進清除棧,用后綴LC。
• Leave函數,且刪除對象,用后綴LD。
4. 函數參數
• 函數參數用a作為前綴。
• 避免出現和匈牙利混合的命名規則如apBuffer
名稱。用aBuffer
即可。
• 函數參數比較多時,應考慮用結構代替。
• 如果不能避免函數參數比較多,應在排版上可考慮每個參數占用一行,參數名豎向對齊。
5. 成員變量
• 成員變量用m最為前綴。
• 避免出現和匈牙利混合的命名規則如mpBuffer名稱。用mBuffer即可。
6. 局部變量
• 循環變量和簡單變量采用簡單小寫字符串即可。例如,int i;
• 指針變量用p
打頭,例如void* pBuffer;
7. 全局變量
• 全局變量用g_
最為前綴。
8. 類名
• 類和對象名應是名詞。
• 實現行為的類成員函數名應是動詞。
• 類的存取和查詢成員函數名應是名詞或形容詞。
9. 風格兼容性
• 對於移植的或者開源的代碼,可以沿用原有風格,不用C++的命名規范。
四、代碼風格
1. Tab和空格
• 每一行開始處的縮進只能用Tab,建議tab用四個空格代替,感覺tab8個空格略長不美觀。不能用空格,輸入內容之后統一用空格。除了最開始的縮進控制用Tab,其他部分為了對齊,需要使用空格進行縮進。這樣可以避免在不同的編輯器下顯示不對齊的情況。
• 在代碼行的結尾部分不能出現多余的空格。
• 不要在"::","->","."
前后加空格。
• 不要在",",";"
之前加空格。
2. 類型定義和{
• 類,結構,枚舉,聯合:大括號另起一行
3. 函數
• 函數體的{需要新起一行,在{之前不能有縮進。
• 除了特別情況,函數體內不能出現兩個空行。
• 除了特別情況,函數體內不能宏定義指令。
• 在一個函數體內,邏揖上密切相關的語句之間不加空行,其它地方應加空行分隔。
• 在頭文件定義的inline
函數,函數之間可以不用空行,推薦用一個空行。
4. 代碼塊
• "if"、"for"、"while"、"do"、"try"、"catch"
等語句自占一行,執行語句不得緊跟其后。不論執行語句有多少都要加 “{ }” 。這樣可以防止書寫和修改代碼時出現失誤。
• "if"、"for"、"while"、"do"、"try"、"catch"
的括號和表達式,括號可緊挨關鍵字,這樣強調的是表達式。
5. else
• if語句如果有else語句,用 } else { 編寫為一行,不推薦用 3 行代碼的方式。
6. 代碼行
• 一行代碼只做一件事情,如只定義一個變量,或只寫一條語句。這樣的代碼容易閱讀,並且方便於寫注釋。
• 多行變量定義,為了追求代碼排版美觀,可將變量豎向對齊。
• 代碼行最大長度宜控制在一定個字符以內,能在當前屏幕內全部可見為宜。
7. switch語句
• case關鍵字應和switch對齊。
• case子語句如果有變量,應用{}包含起來。
• 如果有並列的類似的簡單case語句,可考慮將case代碼塊寫為一行代碼。
• 簡單的case之間可不用空行,復雜的case之間應考慮用空行分割開。
• case字語句的大括號另起一行,不要和case寫到一行。
• 為所有switch語句提供default分支。
• 若某個case不需要break一定要加注釋聲明。
8. 循環
• 空循環可用 for( ;; )
或者 while( 1 )
或者 while( true )
9. 類
• 類繼承應采用每個基類占據一行的方式。
• 單繼承可將基類放在類定義的同一行。如果用多行,則應用Tab縮進。
• 多繼承在基類比較多的情況下,應將基類分行,並采用Tab縮進對齊。
• 重載基類虛函數,應在該組虛函數前寫注釋 // implement XXX
• 友元聲明放到類的末尾。
10. 宏
• 不要用分號結束宏定義。
• 函數宏的每個參數都要括起來。
• 不帶參數的宏函數也要定義成函數形式。
11. goto
• 盡量不要用goto
。
五、類型
• 定義指針和引用時*和&緊跟類型。
• 盡量避免使用浮點數,除非必須。
• 用typedef
簡化程序中的復雜語法。
• 避免定義無名稱的類型。例如:typedef enum { EIdle, EActive } TState;
• 少用union,如果一定要用,則采用簡單數據類型成員。
• 用enum
取代(一組相關的)常量。
• 不要使用魔鬼數字。
• 盡量用引用取代指針。
• 定義變量完成后立即初始化,勿等到使用時才進行。
• 如果有更優雅的解決方案,不要使用強制類型轉換。
六、表達式
• 避免在表達式中用賦值語句。
• 避免對浮點類型做等於或不等於判斷。
• 不能將枚舉類型進行運算后再賦給枚舉變量。
• 在循環過程中不要修改循環計數器。
七、函數
1. 引用
• 引用類型作為返回值:函數必須返回一個存在的對象。
• 引用類型作為參數:調用者必須傳遞一個存在的對象。
2. 常量成員函數
• 表示該函數只讀取對象的內容,不會對對象進行修改。
3. 返回值
• 除開void
函數,構造函數,析構函數,其它函數必須要有返回值。
• 當函數返回引用或指針時,用文字描述其有效期。
4. 內聯函數
• 內聯函數應將函數體放到類體外。
• 只有簡單的函數才有必要設計為內聯函數,復雜業務邏輯的函數不要這么做。
• 虛函數不要設計為內聯函數。
5. 函數參數
• 只讀取該參數的內容,不對其內容做修改,用常量引用。
• 修改參數內容,或需要通過參數返回,用非常量應用。
• 簡單數據類型用傳值方式。
• 復雜數據類型用引用或指針方式。
八、類
1. 構造函數
• 構造函數的初始化列表,應和類的順序一致。
• 初始化列表中的每個項,應獨占一行。
• 避免出現用一個成員初始化另一個成員。
• 構造函數應初始化所有成員,尤其是指針。
• 不要在構造函數和析構函數中拋出異常。
2. 純虛函數
• M類的虛函數應設計為純虛函數。
3. 構造和析構函數
• 如果類可以繼承,則應將類析構函數設計為虛函數。
• 如果類不允許繼承,則應將類析構函數設計為非虛函數。
• 如果類不能被復制,則應將拷貝構造函數和賦值運算符設計為私有的。
• 如果為類設計了構造函數,則應有析構函數。
4. 成員變量
• 盡量避免使用mutable
和Volatile
。
• 盡量避免使用公有成員變量。
5. 成員函數
• 努力使類的接口少而完備。
• 盡量使用常成員函數代替非常成員函數,const
函數
• 除非特別理由,絕不要重新定義(繼承來的)非虛函數。(這樣是覆蓋,基類的某些屬性無初始化)
6. 繼承
• 繼承必須滿足IS-A的關系,HAS-A應采用包含。
• 虛函數不要采用默認參數。
• 除非特別需要,應避免設計大而全的虛函數,虛函數功能要單一。
• 除非特別需要,避免將基類強制轉換成派生類。
7. 友元
• 盡量避免使用友元函數和友元類。
九、錯誤處理
• 申請內存用new操作符。
• 釋放內存用delete操作符。
• new
和delete
,new[]
和delete[]
成對使用。
• 申請內存完成之后,要檢測指針是否申請成功,處理申請失敗的情況。
• 誰申請誰釋放。優先級:函數層面,類層面,模塊層面。
• 釋放內存完成后將指針賦空,避免出現野指針。
• 使用指針前進行判斷合法性,應考慮到為空的情況的處理。
• 使用數組時,應先判斷索引的有效性,處理無效的索引的情況。
• 代碼不能出現編譯警告。
• 使用錯誤傳遞的錯誤處理思想。
• 衛句風格:先處理所有可能發生錯誤的情況,再處理正常情況。
• 嵌套do-while(0)
宏:目的是將一組語句變成一個語句,避免被其他if等中斷。