C/C++語言編碼規範
目錄彔彔
1.1. 起個合適的名字
² 類的名稱要能告訴我們,這個類是什麼。因此,類的名稱通常是名詞。
² 類的名字不需要告訴我們,它從哪個類繼承而來的。
² 有時候加個字尾是很有用的。比如類是一個代理(Agents)時,起名叫DownloadAgent更能表達真實的意圖。
² 方法和函式通常都要執行某種行為,因此,名稱要能清楚的說明它做什麼:CheckForErrors() 而不是ErrorCheck(),DumpDataToFile() 而不是 DataFile()。 這樣也可以很容易的區別函式和資料。
² 函式名總以動詞開頭,後面跟隨其它名稱。這樣看起來更自然些。
² 可以加一些必要的字尾:
Max – 表示取最大值
Cnt – 表示當前的計數值
Key – 表示鍵值
例如:RetryMax 表示可接收的最大數,RetryCnt表示當前接收的數量。
² 字首也同樣有用:
Is – 用於詢問一些問題。只要看到Is開頭,就知道這是一個查詢。
Get – 用於獲取一個值。
Set – 用於設定一個值。
例如:IsHitRetryLimit.
1.1.3. 含有度量單位的名稱(適用於C/C++)
² 如果一個變數用於表示時間,重量或其它度量單位,應把度量單位新增到名稱中,以便開發人員更早一步發現問題。
例如:
uint32 mTimeoutMsecs;
uint32 mMyWeightLbs;
² 無論是什麼縮寫名稱,我們總以一個大寫字母開頭,後面跟隨的字母全部用小寫。
例如:
class FluidOz; // 而不是 FluidOZ
class NetworkAbcKey; // 而不是 NetworkABCKey
1.2.類的命名(適用於C++)
² 用大寫字母作為單詞的分隔,每個單詞的首字母大寫,其它字母均小寫。
² 名字的第一個字母應大寫
² 不含有下劃線 ('_')
例如:
class NameOneTwo;
class Name;
1.3.類庫(或程式庫)命名 (適用於C/C++)
² 使用名稱空間防止名字衝突。
² 如果編譯器沒有實現名稱空間,需要用字首來避名名字衝突,不過字首不要過長(2個字母比較好)。
例如:
John Johnson 完成了一個數據結構的庫,它可以使用JJ作為庫的字首,所以類名就象下面這樣:
class JjLinkList
{
}
1.4.方法和函式的命名(適用於C++)
² 使用與類名相同的規則
例如:
class NameOneTwo
{
public:
int DoIt();
void HandleError();
}
1.5.類屬性的命名(適用於C++)
² 屬性(通常是非公有資料成員)名字以字母'm'開頭。
² 在 'm(m_)' 後面,使用與類名相同的規則。
² 'm(m_)' 總是位於其它修飾符(如表示指標的 'p')的前面。
例如:
class NameOneTwo
{
public:
int VarAbc();
int ErrorNumber();
private:
int mVarAbc;
int mErrorNumber;
String* mpName;
}
1.6.方法和函式引數的命名(適用於C++)
² 第一個字母必須小寫。
² 第一個字母后面的單詞使用與類名相同的規則。
例如:
class NameOneTwo
{
public:
int StartYourEngines(
Engine&rSomeEngine,
Engine&rAnotherEngine);
}
1.7.區域性變數的命名(適用於C/C++)
² 所有字母都用小寫
² 使用下劃線 '_' 作為單詞的分隔。
例如:
int
NameOneTwo::HandleError(int errorNumber)
{
int error= OsErr();
Time time_of_error;
ErrorProcessor error_processor;
}
1.8.指標變數的命名字首(適用於C/C++)
² 指標變數多數情況應在前面加 'p'。
² 星號 '*' 應靠近型別,而不是變數名。
例如:
String* pName=new String;
特別的:String* pName, name; 應分成兩行來寫:
String* pName;
String name;
1.9.引用變數和返回引用函式的命名字首(適用於C++)
² 引用必須用 'r'作字首修飾。
例如:
class Test
{
public:
void DoSomething(StatusInfo&rStatus);
StatusInfo& rStatus();
constStatusInfo& Status() const; // 這裡返回的是常量引用,所以不符合本規則
private:
StatusInfo& mrStatus;
}
1.10. 全域性變數的命名字首(適用於C/C++)
² 全域性變數總是以 'g(g_)' 作為字首。
例如:
Logger g_Log;
Logger* g_pLog;
1.11. 全域性常量的命名(適用於C/C++)
² 全域性常量全部大寫,並以下劃線 '_' 分隔單詞。
例如:
const intA_GLOBAL_CONSTANT = 5;
1.12. 靜態變數的命名字首(適用於C++)
² 靜態變數以 's' 作為字首。
例如:
class Test
{
public:
private:
staticStatusInfo m_sStatus;
}
1.13. 自定義型別(typedef)的命名(適用於C/C++)
² 型別定義名稱指的是用typedef定義的名稱。
² 型別定義名稱使用與類名相同的規則,並使用Type作為字尾。
例如:
typedefuint16 ModuleType;
typedefuint32 SystemType;
1.14. 巨集定義的命名(適用於C/C++)
² 所有單詞的字母都用大寫,並使用下劃線 '_' 分隔.
例如:
#define MAX(a,b) blah
#define IS_ERR(err) blah
1.15. C 函式的命名(適用於C/C++)
² C++專案中,應儘量少用C函式。
² C函式使用GNU規範,所有字母都使用小寫,並用下劃線 '_' 作為單詞的分隔。
例如:
int
some_bloody_function()
{
}
² 特別的,為了賺容C/C++,在必要的時候,在C++中應以下面的格式定義C函式:
extern “C” int some_bloody_function();
² 或在C/C++中推薦使用下面的格式:
#ifdef__cplusplus__
extern “C”{
#endif
int
some_bloody_function()
{
}
#ifdef__cplusplus__
}
#endif
1.16. 列舉的命名(適用於C/C++)
² 所有字母都大寫,並用下劃線 '_' 作為單詞分隔。
例如:
enumPinStateType
{
PIN_OFF,
PIN_ON
};
enum { STATE_ERR, STATE_OPEN, STATE_RUNNING, STATE_DYING};
2. 排版規則
² 請使用下面的模板來建立一個新的類:
/**
* 用一行來描述類
*
*#include "XX.h" <BR>
*-llib
*
* 類的詳細說明
*
* @seesomething
*/
#ifndef SORUTION_PROJECT_CLASSNAME_H
#define SORUTION_PROJECT_CLASSNAME_H
// 在這裡包含系統標頭檔案
//
// 在這裡包含專案標頭檔案
//
// 在這裡包含區域性標頭檔案
//
// 在這裡放置前置引用
//
class XX
{
public:
// 類的生命週期控制函式,如構造和析構,以及狀態機
/**
*Default constructor.
*/
XX(void);
/**
*Copy constructor.
*
*@param from The value to copy to this object.
*/
XX(const XX& from);
/**
*Destructor.
*/
virtual ~XX(void);
// 在這裡放置類的運算操作符
/**
*Assignment operator.
*
*@param from THe value to assign to this object.
*
*@return A reference to this object.
*/
XX& operator=(XX&from);
// 在這裡放置類的操作
// 在這裡放置屬性存取
// 在這裡放置類的狀態查詢
protected:
private:
};
// 內聯方法定義
//
// 外部引用
//
#endif // SORUTION_PROJECT_CLASSNAME_H
² 定義的順序是: public, protected, private
² 要清楚public/protected/private都應該放置哪些東西
2.1.2. 原始檔格式(適用於C++)
#include "XX.h" // class implemented
/////////////// PUBLIC///////////////////////
//================= 建構函式 ====================
XX::XX()
{
}// XX
XX::XX(const XX&)
{
}// XX
XX::~XX()
{
}// ~XX
//=============== 操作符=========================
XX&
XX::operator=(XX&);
{
return *this;
}// =
//==============類的操作 =====================
//==============屬性存取 =====================
//==============狀態查詢 =====================
///////////// PROTECTED //////////////////
///////////// PRIVATE //////////////////
² 應使用巨集定義來保護標頭檔案不被重複包含:
#ifndef SORUTION_PROJECT_CLASSNAME_H
#define SORUTION_PROJECT_CLASSNAME_H
#endif // SORUTION_PROJECT_CLASSNAME_H
² 如果使用名稱空間的時候,要把名稱空間加到檔名前面:
#ifndef SORUTION_PROJECT_NAMESPACE_CLASSNAME_H
#define SORUTION_PROJECT_NAMESPACE_CLASSNAME_H
#endif
² 對於有較多引數的函式的寫法
如果引數較多,一行寫不下,我們應該分成幾行來寫,並且每個引數都另起一行對齊:
int AnyMethod(
int arg1,
int arg2,
int arg3,
int arg4); 或
int AnyMethod( int arg1
, int arg2
, int arg3
, int arg4);
² 縮排的時候,每一層縮排3,4,或8個空格。(推薦使用4個空格)
² 不要使用TAB,用空格,大多數編輯器可以用空格代替TAB。TAB應固定4個空格,因為大多數編輯器都是這麼設定的。
² 雖然沒有規定縮排的層次,但是4至5層是合適的。如果縮排的層次太多,你可能需要考慮是否進行程式碼重構了。
例如:
void
func()
{
if (something bad)
{
if (another thing bad)
{
while (more input)
{
}
}
}
}
² 有許多編輯器螢幕只有78個字母寬
² 一行最多隻寫一條語句
² 一行只定義一個變數
例如:
不要象下面這樣:
char** a, *x;
int width, height; //widthand height of image
要象這樣:
char** a= 0; // 文件說明
char* x= 0; // 文件說明
² 在關鍵字的下一行單獨放置括號,並且與關鍵字對齊,如:
if (condition)
{
...
}
while (condition)
{
…
}
所有的 if, while 和 do 語句,要麼用單行格式,要麼使用花括號格式。
² 使用花括號格式:
if (1 == somevalue)
{
somevalue = 2;
}
² 單行格式:
if (1 == somevalue) somevalue = 2;
或下面這樣(對於這種寫法,建議使用花括號):
if (1 == somevalue)
{
somevalue = 2;
}
² 在花括號結束的位置加上註釋是一個好習慣。假如前後花括號距離很遠,註釋就能幫你理解它是如何對應的。如:
while(1)
{
if (valid)
{
} // if valid
else
{
} // not valid
} // end forever
² 一個語句塊儘量不超過一個螢幕大小,這樣,不要捲動螢幕就可以閱讀程式碼。
2.6.圓括號 () 規則 (適用於C/C++)
² 圓括號與關鍵字之間應放一個空格。
² 圓括號與函式名之間不要有空格。
² Return 語句不要使用圓括號。
例如:
if (condition)
{
}
while(condition)
{
}
strcpy(s, s1);
return 1;
2.7.if else 語句的格式 (適用於C/C++)
² 佈局
if (條件) // 註釋
{
}
else if (條件) // 註釋
{
}
else // 註釋
{
}
² 條件格式
總是把常量放在等號或不等於號的左邊:
if ( 6 == errorNum ) ...
一個很重要的理由是,假如漏寫一個等號,這種寫法會產生一個編譯錯誤,有助於馬上發現問題。
比如:
if ( errorNum == 6) ...
錯寫成:
if ( errorNum = 6) ... // 這是一個不容易發現的災難
2.8.switch 格式 (適用於C/C++)
² 直通的case語句,應該放置一條註釋說明這個case語句是直通到下一個case語句的。
² 總是要寫default語句,不管是否是需要。
² 在case中需要定義變數的時候,應把所有程式碼放在語句塊中。
例如:
switch (...)
{
case 1:
...
// 繼續執行case2
case 2:
{
int v;
...
}
break;
default:
}
² 儘量避免使用Goto 語句。一個合理使用goto語句的場合是,當你需要從多層迴圈中跳出。例如:
for (...)
{
while (...)
{
...
if (disaster)
goto error; //跳出迴圈
}
}
...
error:
clean up the mess
² 跳轉的標號必須單獨在一行的最左邊。Goto語句需要有相應的註釋,說明它的用途。
² Continue 和break 實際上起到與goto一樣的作用,因此,儘量少用為上。並且,Continue與break最好不要連用。
² 用括號把條件表示式括起來。
² 不要在 ? : 中寫上過多的程式碼,操作表示式應儘可能簡潔。
² 操作語句應分行寫,除非它們能夠簡潔的放在一行當中。
例如:
(condition) ?funct1() : func2();
或
(condition)
? longstatement
: anotherlong statement;
2.10. 運算子號的規則 (適用於C/C++)
² 一元操作符如(!、~ 等等)應貼近操作物件。
如:
if (!IsOk)
return ++v;
² 二元操作符如(+、*、%、== 等等)應在前後留空格。
如:
if ( v1 == v2)
return v1 * 3;
² ++ 和 -- 儘量使用前置運算。在C++中,不管 ++i 還是 i++,總是++i更容易生成優化程式碼。
如:
for(int i = 0; i < 10; ++i)
² 變數應該是隨用隨宣告,不要集中在函式前(有些C語言不支援,則不在此要求之列)。特別是在for語句的迴圈變數,應只在for語句中定義。
如:
for(int i = 0; i < 10; ++i)
² 宣告語句塊必須要對齊
型別,變數,等號和初始化值要分別對齊。
例如:
DWORD mDword;
DWORD* mpDword;
char* mpChar;
char mChar;
mDword = 0;
mpDword = NULL;
mpChar = NULL;
mChar = 0;
3. 文件及註釋
應當使用文件自動生成工具,來生成相關的程式文件。
可以為整個檔案編寫文件。
例如:
/** @file file.h
* Abrief file description.
* Amore elaborated file description.
*/
在類定義前面應加上類說明文件。
例如:
/** WindowsNT
* @brief Windows Nice Try.
* @author Bill Gates
* @author Several species of small furryanimals gathered together
* in a cave and grooving with a pict.
* @version 4.0
* @date 1996-1998
* @bug It crashes a lot and requires hugeamounts of memory.
* @bug The class introduces the more bugs, thelonger it is used.
* @warning This class may explode in your face.
* @warning If you inherit anything from thisclass, you're doomed.
*/
class WindowsNT {};
² 函式註釋
所有的引數都應該有文件說明(param),所有的返回程式碼都應該有文件說明(return),所有的例外都應該有文件說明(exception)。可以使用(see)引用有關的開發資源。如:
/**
* 賦值操作符
*
*@param val 將要賦給本物件的值
*
*@return 本物件的引用
*/
XX& operator =(XX& val);
² 註釋屬性
一些自動文件工具定義的屬性可以包含在文件中,常用的有:
n 前提條件 (pre)
定義呼叫這個函式的前提條件
n 警告說明 (warning)
定義一些關於這個函式必須知道的事情。
n 備註說明 (remarks)
定義一些關於這個函式的備註資訊。
n 將要完成的工作 (todo)
說明哪些事情將在不久以後完成
n 使用例子說明 (example)
一個圖片能表達100句話,一個好的例子能解答1000個問題。
例如:
/**
* 複製一個字串
*
*@pre
* - 需要保證(from != 0)
* - 需要保證(to != 0)
*
*@warning
* 緩衝區必需足夠大,以便容納的下要拷貝的字串。
*
*@example teststrcpy.cpp
*
*@param from 要拷貝的字串
*@param to 用於容納字串的緩衝區
*@return void
*/
void strcpy(constchar* from, char* to);
² 如果有必要,#include語句也應有註釋,它可以告訴我們,為什麼要包含這個標頭檔案。
3.5.語句塊註釋(適用於C/C++)
² 語句塊的註釋可以用在語句塊的開頭和結束位置:
{
// Block1 (meaningful comment about Block1)
... some code
{
// Block2 (meaningful comment about Block2)
... somecode
} // End Block2
} // End Block1
² 編譯器的警告,通常能夠指示出編碼存在的筆誤或邏輯錯誤。因此,不能輕視編譯器的任何警告。正確的作法是,不允許程式碼在編譯時產生任何警告資訊。
² 根據開發規模,選擇合適的原始碼管理器。使用原始碼管理器是非常必要的。
² 預設建構函式(DefaultConstructor)
如果建構函式的所有引數都是可選的,那麼這個建構函式也是預設建構函式。如果沒有定義任何普通建構函式,則編譯將自動生成一個。
² 虛解構函式(Virtual Destructor)
如果一個類可以被繼承,那麼應該使用虛解構函式。如果沒有定義虛解構函式,則編譯器將自動生成一個。
² 拷貝建構函式(Copy Constructor)
如果一個類不應該被拷貝,應該定義一個私有的拷貝建構函式,並且不定義它的實現。如果不知道一個類是否應該被拷貝,就認為它是不可拷貝的,直到你確認它應該被拷貝。如果沒有定義拷貝建構函式,則編譯器將自動生成一個。
² 賦值操作(AssignmentOperator)
如果一個類不應該被賦值,應該定義一個私有的賦值操作函式,並且不定義它的實現。如果不知道一個類是否應該被賦值,就認為它是不可賦值的,直到你確認它應該被賦值。如果沒有定義賦值操作函式,則編譯器將自動生成一個。
² 命名規則
根名字一般是設計者的名字。比如公司名稱等等。
² 不要在全域性空間使用using語句。
² 無論如何,都要初始化所有的變數。我們無法保證編譯器會給個什麼樣的初值。
4.6. 保持函式短小精悍(適用於C/C++)
² 一般情況下,一個函式最好在一個螢幕內,不要超過三個螢幕。
4.7.對空語句進行註釋 (適用於C/C++)
² For和while語句如果跟隨一個空語句,需要對此語句進行註釋,並且空語句應另起一行。如:
while(*dest++ = *src++)
; // VOID
² 不允許寫成:
while (*dest++ = *src++) ; // 絕對不允許這麼寫
² If語句只用於檢測布林值(bool),不要用預設的方法測試非零值,比如:
建議使用:
if (FAIL != f())
不建議使用下面的表示式:
if (f())
² 巨集定義的情況也一樣:
#define STREQ(a,b) (strcmp((a), (b)) == 0)
或者使用行內函數:
inline bool
StringEqual(char* a, char* b)
{
(strcmp(a, b)== 0) ? return true : return false;
Or more compactly:
returnstrcmp(a, b) == 0;
}
4.9.布林型別 (適用於C/C++)
² 早期的C++沒有布林型別,但新的C++標準增加了布林型別。如果可以使用內建的布林型別的情況下,應使用布林型別。
早期的布林型別定義為:
typedef int bool;
#defineTRUE 1
#defineFALSE 0
或:
const intTRUE = 1;
const int FALSE= 0;
² 在這種情況下,條件表示式不要比較1值(如TRUE,YES等等),而要用0值(如FALSE,NO等等)進行比較。因為多數函式返回0表示FALSE,而非零表示TRUE。如:
if (TRUE ==func()) { ... // 錯誤:假如func()返回 2 怎麼辦?
必須寫成:
if (FALSE !=func()) { ...
4.10. 避免在語句中內含賦值 (適用於C/C++)
² 只有一種情況可以在語句中內含賦值,它要能使程式碼顯得更易理解,例如:
while (EOF != (c= getchar()))
{
process thecharacter
}
² ++ 和 -- 操作也是一種賦值語句
² 內含賦值語句常常會帶來一些副作用。在遇到這種情況時,我們應分成幾個語句來寫。比如:
a = b + c;
d = a + r;
不應該寫成:
d = (a = b + c)+ r;
4.11. 正確的使用Const (適用於C/C++)
² C/C++ 提供const 關鍵字,用於指示不應該被修改的物件或資料。正確的使用Const既可以提供編譯器的優化指示,也能夠避免一些編碼錯誤。
4.12. 不要在標頭檔案定義資料(適用於C/C++)
不要把資料定義放在標頭檔案,如:
/*
* aheader.h
*/
int x = 0;
4.13. 不要直接使用數字 (適用於C/C++)
² 直接使用數字,會使原始碼難以理解和維護。如:
if (22 ==foo) { start_thermo_nuclear_war(); }
else if (19 == foo) {refund_lotso_money(); }
else if (16 == foo) {infinite_loop(); }
else { cry_cause_im_lost(); }
當一段時間過去以後,有誰會記得22和19是什麼意思?假如數字改變,或者是編寫錯誤,更是難以發現問題。
² 我們可以用#define或者常量來改變這一狀況,如:
#define PRESIDENT_WENT_CRAZY (22)
const int WE_GOOFED= 19;
enum
{
THEY_DIDNT_PAY=16
};
if (PRESIDENT_WENT_CRAZY == foo) { start_thermo_nuclear_war(); }
else if (WE_GOOFED == foo) {refund_lotso_money(); }
else if (THEY_DIDNT_PAY == foo) { infinite_loop();}
else {happy_days_i_know_why_im_here(); }
² 如果可以,使用行內函數代替巨集。
例如:
#ifndef MAX
#define MAX(x,y) (((x) > (y) ? (x) : (y)) // 取最大數
#endif
使用行內函數可以達到相同的效果,而且更安全:
inline int
max(int x, inty)
{
return (x> y ? x : y);
}
² 要注意副作用
必須小心副作用,因為在呼叫表示式時,會發生潛在的錯誤。
例如:
MAX(f(x),z++);
² 表示式總是用括號括起來
在巨集展開時,使用括號可以避免巨集展開後產生的二義性。
例如:
#define ADD(x,y) x + y
必須寫成:
#define ADD(x,y) ((x) + (y))
² 保證巨集名稱的唯一性
和全域性變數一樣,巨集也會與其它名稱產生衝突。下面兩條規則有助於解決這個問題:
n 在巨集名稱前加上庫的名字
避免使用簡單而常用的名字,如:MAX 和MIN。