天天看點

C/C++ 程式設計規範

1.   檔案結

每個C++/C  程式通常分為兩個檔案。一個檔案用于儲存程式的聲明(declaration),

稱為頭檔案。另一個檔案用于儲存程式的實作( implementation ),稱為定義( definition) C++/C  程式的頭檔案以“.h”為字尾,C  程式的定義檔案以“. c”為字尾,C++程式 的定義檔案通常以“.cpp”為字尾(也有一些系統以“.cc”或“.cxx”為字尾)。

1.1 版權和版本的聲明

版權和版本的聲明位于頭檔案和定義檔案的開頭(參見示例1-1),主要内容有:

(1)版權資訊

(2)檔案名稱,辨別符,摘要

(3)目前版本号,作者/修改者,完成日期

(4)版本曆史資訊

#ifndef GRAPHICS_H //  防止graphics.h  被重複引用

#define GRAPHICS_H 下面其它的聲明代碼

……… 

下面是原作者、版本、完成、日期和目前版本的資訊

示例 1-1 版權和版本的聲明

版本辨別:采用 <主版本号>.<次版本号>.<修訂号> 來命名自己産品的編号。Linux 核心還有一個約定, 就是如果次版本号是偶數(如 0、2、4 等),代表正式版本,如果次版本号是奇數(如 1、3、5 等),代 表的是開發過程中的測試版本。修訂号則相當于 Build 号,用來辨別一些小的改動。

1.2 頭檔案的結構

頭檔案由三部分内容組成:

1)頭檔案開頭處的版權和版本聲明(參見示例1-1)。

2)預處理塊。

第 1  頁 共 18  頁

3)函數和類結構聲明等。 假設頭檔案名稱為graphics.h,頭檔案的結構參見示例1-2。

【規則1-2-1】為了防止頭檔案被重複引用,應當用ifndef/define/endif  結構産生預處理塊。

【規則1-2-2】用#include < filename.h>  格式來引用标準庫的頭檔案(編譯器将從标準庫目錄開始搜 索)。

【規則1-2-3】用#include “filename.h” 格式來引用非标準庫的頭檔案(編譯器将從使用者的工作目錄 開始搜尋)。

【建議1-2-1】頭檔案中隻存放“聲明”而不存放“定義”

【建議1-2-2】不提倡使用全局變量,盡量不要在頭檔案中出現象extern int value  這類聲明。

// 版權和版本聲明見示例1-1,此處省略。

#ifndef GRAPHICS_H //  防止graphics.h  被重複引用

#define GRAPHICS_H

#include <math.h> //  引用标準庫的頭檔案

#include “myheader.h”   //  引用非标準庫的頭檔案

void Function1(… ); //  全局函數聲明

class Box //  類結構聲明

{ …

};

#endif

示例1-2 C++/C 頭檔案的結構

1.3 定義檔案的結構

定義檔案有三部分内容:

1) 定義檔案開頭處的版權和版本聲明(參見示例1-1)。

2) 對一些頭檔案的引用。

3) 程式的實作體(包括資料和代碼)。 假設定義檔案的名稱為graphics.cpp,定義檔案的結構參見示例1-3

// 版權和版本聲明見示例1-1,此處省略。

#include “graphics.h”  //  引用頭檔案

// 全局函數的實作體

void Function1(… )

{

...

}

// 類成員函數的實作體

void Box::Draw(...)

{

...

}

示例1-3 C++/C 定義檔案的結構

1.4 目錄結構

如果一個軟體的頭檔案數目比較多(如超過十個),通常應将頭檔案和定義檔案分别儲存于不同的 目錄,以便于維護。

例如可将頭檔案儲存于include  目錄,将定義檔案儲存于source  目錄(可以是多級目錄)。 如果某些頭檔案是私有的,它不會被使用者的程式直接引用,則沒有必要公開其“聲明”。為了加強

資訊隐藏,這些私有的頭檔案可以和定義檔案存放于同一個目錄。

2.  程式版式

2.1 空行

空行起着分隔程式段落的作用。空行得體(不過多也不過少)将使程式的布局更加清晰。空行不會 浪費記憶體,是以不要舍不得用空行。

【規則2-1-1】在每個類聲明之後、每個函數定義結束之後都要加空行。參見示例2-1(a)

【規則2-1-2】在一個函數體内,邏輯上密切相關的語句之間不加空行,其它地方應加空行分隔。參見 示例2-1(b)

// 空行

void Function1(.)

{

...

}

// 空行

void Function2(.)

{

...

}

// 空行

void Function3(.)

{

...

}

// 空行

while (condition)

{

statement1;

// 空行

if (condition)

{

statement2;

}

else

{

statement3;

}

// 空行

statement4;

}

示例2-1(a) 函數之間的空行 示例2-1(b) 函數内部的空行

2.2 代碼行

【規則2-2-1】一行代碼隻做一件事情,如隻定義一個變量,或隻寫一條語句。這樣的代碼容易閱讀, 并且友善于寫注釋。

【規則2-2-2】if、for、while、do  等語句自占一行,執行語句不得緊跟其後。不論執行語句有多少都 要加{}。這樣可以防止書寫失誤。

【建議2-2-1】盡可能在定義變量的同時初始化該變量(就近原則) 如果變量的引用處和其定義處相隔比較遠,變量的初始化很容易被忘記。如果引用了未被初始化的

變量,可能會導緻程式錯誤。本建議可以減少隐患。例如

int width = 10; // 定義并初绐化width int height = 10; // 定義并初绐化height int depth = 10; // 定義并初绐化depth

int width; // 寬度

int height; // 高度

int depth; // 深度

int width, height, depth; // 寬度高度深度

x = a + b;

y = c + d;

z = e + f;

X = a + b; y = c + d; z = e + f;

if (width < height)

{

dosomething();

}

if (width < height) dosomething();

for (initialization; condition; update)

{

dosomething();

}

// 空行

other();

for (initialization; condition; update)

dosomething();

other();

示例2-2(a) 風格良好的代碼行 示例2-2(b) 風格不良的代碼行

2.3 代碼行内的空格

【規則2-3-1】關鍵字之後要留白格。象const、virtual、inline、case 等關鍵字之後至少要留一個 空格,否則無法辨析關鍵字。象if、for、while 等關鍵字之後應留一個空格再跟左括号‘(’,以突 出關鍵字。

【規則2-3-2】函數名之後不要留白格,緊跟左括号‘(’,以與關鍵字差別。

【規則2-3-3】‘(’向後緊跟,‘)’、‘,’、‘;’向前緊跟,緊跟處不留白格。

【規則2-3-4】‘,’之後要留白格,如Function(x, y, z)。如果‘;’不是一行的結束符号,其後要 留白格,如for (initialization; condition; update)。

【規則2-3-5】指派操作符、比較操作符、算術操作符、邏輯操作符、位域操作符,如 “=”、“+=”“>=”、

“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等二進制操作符的前後應當加空格。

【規則2-3-6】一進制操作符如“!”、“~”、“++”、“--”、“&”(位址運算符)等前後不加空格。

【規則2-3-7】象“[]”、“.”、“->”這類操作符前後不加空格。

【建議2-3-1】對于表達式比較長的for 語句和if 語句,為了緊湊起見可以适當地去掉一些空格,如for

(i=0; i<10; i++)和if ((a<=b) && (c<=d))

void Func1(int x, int y, int z); // 良好的風格

void Func1 (int x,int y,int z); // 不良的風格

if (year >= 2000) // 良好的風格

if(year>=2000) // 不良的風格 if ((a>=b) && (c<=d)) // 良好的風格 if(a>=b&&c<=d) // 不良的風格

for (i=0; I<10; i++) // 良好的風格

for(i=0;i<10;i++) // 不良的風格

for (i = 0; I < 10; i ++) // 過多的空格

x = a < b ? a : b; // 良好的風格

x=a<b?a:b; // 不好的風格

int *x = &y; // 良好的風格

int * x = & y; // 不良的風格

array[5] = 0; // 不要寫成array [ 5 ] = 0;

a.Function(); // 不要寫成a . Function();

b->Function(); // 不要寫成 b -> Function();

2.4 對齊

【規則2-4-1】程式的分界符‘{’和‘}’應獨占一行并且位于同一列,同時與引用它們的語句左對齊。

【規則2-4-2】{ } 之内的代碼塊在‘{’右邊數格處左對齊。

void Function(int x)

{

… // program code

}

void Function(int x){

... // program code

}

if (condition)

{

... // program code

}

else

{

... // program code

if (condition){

... // program code

}

else {

... // program code

}

}

for (initialization; condition; update)

{

... // program code

}

for (initialization; condition; update){

... // program code

}

While (condition)

{

... // program code

}

while (condition){

... // program code

}

如果出現嵌套的{},則使用縮進對齊,如:

{

...

{

...

}

...

}

示例2-4(a) 風格良好的對齊 示例2-4(b) 風格不良的對齊

2.5 長行拆分

【規則2-5-1】代碼行最大長度宜控制在70 至80 個字元以内。代碼行不要過長,否則看不過來,也不 便于列印。

【規則2-5-2】長表達式要在低優先級操作符處拆分成新行,操作符放在新行之首(以便突出操作符)。 拆分出的新行要進行适當的縮進,使排版整齊,語句可讀。

if ((very_longer_variable1 >= very_longer_variable12)

&& (very_longer_variable3 <= very_longer_variable14)

&& (very_longer_variable5 <= very_longer_variable16))

{

dosomething();

}

virtual CMatrix CMultiplyMatrix (CMatrix leftMatrix,

CMatrix rightMatrix);

For (very_longer_initialization; very_longer_condition; very_longer_update)

{

dosomething();

}

示例2-5 長行的拆分

2.6 修飾符的位置

【規則2-6-1】将修飾符 * 和 &緊靠變量名 例如: char *name;

int * x, y; // 此處y 不會被誤解為指針

2.7 注釋

C 語言的注釋符為“

const int MAX = 100; // C++ 語言的const 常量

const float PI = 3.14159; // C++ 語言的const 常量

【規則5-1-2】在C++ 程式中隻使用const 常量而不使用宏常量,即const 常量完全取代宏常量。

5.2 常量定義規則

【規則5-2-1】需要對外公開的常量放在頭檔案中,不需要對外公開的常量放在定義檔案的頭部。為便 于管理,可以把不同子產品的常量集中存放在一個公共的頭檔案中。

【規則5-2-2】如果某一常量與其它常量密切相關,應在定義中包含這種關系,而不應給出一些孤立的

值。例如:const float RADIUS = 100;

const float DIAMETER = RADIUS * 2;

6.  函數設計

一個函數的注釋資訊如下例:

int GetValue(int iLength,int iWidth)

{

…… ..

return iArea;

}

1 描述在單元測試中出現的錯誤

2……… .

6.1 參數的規則

【規則6-1-1】參數的書寫要完整,不要貪圖省事隻寫參數的類型而省略參數名字。如果函數沒有參數, 則用void 填充。

例如:void SetValue(int width, int height); // 良好的風格

void SetValue(int, int); // 不良的風格 float GetValue(void); // 良好的風格 float GetValue(); // 不良的風格

【規則6-1-2】參數命名要恰當,順序要合理。 例如:編寫字元串拷貝函數StringCopy,它有兩個參數。如果把參數名字起為str1 和str2,例如

void StringCopy(char *str1, char *str2);

那麼我們很難搞清楚究竟是把str1 拷貝到str2 中,還是剛好倒過來。可以把參數名字起得更有意 義,如叫strSource 和strDestination。這樣從名字上就可以看出應該把strSource 拷貝到 strDestination。另外,這兩個參數那一個該在前那一個該在後?參數的順序要遵循程式員的習慣。一 般地,應将目的參數放在前面,源參數放在後面。

【規則6-1-3】如果參數是指針,且僅作輸入用,則應在類型前加const,以防止該指針在函數體内被意 外修改。

例如:void StringCopy(char *strDestination,const char *strSource);

【規則6-1-4】如果輸入參數以值傳遞的方式傳遞對象,則宜改用“const & ”方式來傳遞,這樣可以省 去臨時對象的構造和析構過程,進而提高效率。

【規則 6-1-5】參數預設值隻能出現在函數的聲明中,而不能出現在定義體中。

【規則 6-1-6】如果函數有多個參數,參數隻能從後向前挨個兒預設,否則将導緻函數調用語句怪模怪 樣。

【建議6-1-1】避免函數有太多的參數,參數個數盡量控制在5 個以内。如果參數太多,在使用時容易 将參數類型或順序搞錯。

【建議6-1-2】盡量不要使用類型和數目不确定的參數。

6.2 傳回值的規則

【規則6-2-1】不要省略傳回值的類型。

C++語言有很嚴格的類型安全檢查,不允許上述情況發生。由于C++程式可以調用C 函數,為了避免 混亂,規定任何C++/ C 函數都必須有類型。如果函數沒有傳回值,那麼應聲明為void 類型。

【規則6-2-2】函數名字與傳回值類型在語義上不可沖突。

【規則6-2-3】不要将正常值和錯誤标志混在一起傳回。正常值用輸出參數獲得,而錯誤标志用return 語 句傳回。

【規則 6-2-4】給以“指針傳遞”方式的函數傳回值加 const 修飾,那麼函數傳回值(即指針)的内容 不能被修改,該傳回值隻能被賦給加 const 修飾的同類型指針。

【規則 6-2-5】函數傳回值采用“值傳遞方式”,由于函數會把傳回值複制到外部臨時的存儲單元中, 加 const 修飾沒有任何價值。

【規則 6-2-6】函數傳回值采用“引用傳遞”的場合并不多,這種方式一般隻出現在類的指派函數中,

目的是為了實作鍊式表達。

6.3 函數内部實作的規則

不同功能的函數其内部實作各不相同,看起來似乎無法就“内部實作”達成一緻的觀點。但根據經 驗,我們可以在函數體的“入口處”和“出口處”從嚴把關,進而提高函數的品質。

【規則6-3-1】在函數體的“入口處”,對參數的有效性進行檢查。

【規則6-3-2】在函數體的“出口處”,對return 語句的正确性和效率進行檢查。

注意事項如下:

(1)return 語句不可傳回指向“棧記憶體”的“指針”或者“引用”,因為該記憶體在函數體結束時被自 動銷毀。例如

char * Func(void)

{

char str[] = “hello world”; // str 的記憶體位于棧上

return str; // 将導緻錯誤

}

(2)要搞清楚傳回的究竟是“值”、“指針”還是“引用”。

(3)如果函數傳回值是一個對象,要考慮return 語句的效率。

6.4 其它建議

【建議6-4-1】函數的功能要單一,不要設計多用途的函數。

【建議6-4-2】函數體的規模要小,盡量控制在50 行代碼之内。

【建議6-4-3】盡量避免函數帶有“記憶”功能。相同的輸入應當産生相同的輸出。帶有“記憶”功能 的函數,其行為可能是不可預測的,因為它的行為可能取決于某種“記憶狀态”。這樣的函數既不易理 解又不利于測試和維護。在C/C++語言中,函數的static 局部變量是函數的“記憶”存儲器。建議盡量 少用static  局部變量,除非必需。

【建議6-4-4】不僅要檢查輸入參數的有效性,還要檢查通過其它途徑進入函數體内的變量的有效性, 例如全局變量、檔案句柄等。

【建議6-4-5】用于出錯處理的傳回值一定要清楚,讓使用者不容易忽視或誤解錯誤情況。

6.5 使用斷言

程式一般分為Debug  版本和Release  版本,Debug  版本用于内部調試,Release  版本發行給使用者 使用。

斷言assert  是僅在Debug  版本起作用的宏,它用于檢查“不應該”發生的情況。在運作過程中, 如果assert  的參數為假,那麼程式就會中止(一般地還會出現提示對話,說明在什麼地方引發了assert)。

assert  不是一個倉促拼湊起來的宏。為了不在程式的Debug  版本和Release  版本引起差别,assert 不應該産生任何副作用。是以assert  不是函數,而是宏。程式員可以把assert  看成一個在任何系統狀态 下都可以安全使用的無害測試手段。如果程式在assert處終止了,并不是說含有該assert 的函數有錯

誤,而是調用者出了差錯,assert 可以幫助我們找到發生錯誤的原因。

【規則6-5-1】使用斷言捕捉不應該發生的非法情況。不要混淆非法情況與錯誤情況之間的差別,後者 是必然存在的并且是一定要作出處理的。

【規則6-5-2】在函數的入口處,使用斷言檢查參數的有效性(合法性)。

【建議6-5-1】在編寫函數時,要進行反複的考查,并且自問:“我打算做哪些假定?”一旦确定了的 假定,就要使用斷言對假定進行檢查。

【建議6-5-2】一般教科書都鼓勵程式員們進行防錯設計,但要記住這種程式設計風格可能會隐瞞錯誤。當 進行防錯設計時,如果“不可能發生”的事情的确發生了,則要使用斷言進行報警。

6.6 引用與指針的比較 引用是C++中的概念,容易把引用和指針混淆。 引用的一些規則如下:

(1)引用被建立的同時必須被初始化(指針則可以在任何時候被初始化)。

(2)不能有NULL 引用,引用必須與合法的存儲單元關聯(指針則可以是NULL)。

(3)一旦引用被初始化,就不能改變引用的關系(指針則可以随時改變所指的對象)。

7  重載和内聯

7.1 普通函數重載

【規則 7-1-1】重載函數中的參數不同(包括類型、順序不同),才是重載函數,而僅僅傳回值不同 則不行。

【規則 7-1-2】當心隐式類型轉換導緻重載函數産生二義性,數字本身沒有類型,将數字當作參數時

将自動進行類型轉換(稱為隐式類型轉換)。 成員函數的重載、覆寫與隐藏

【規則 7-2-1】成員函數的重載、覆寫(override)與隐藏很容易混淆,注意區分。

【規則 7-2-2】注意如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無 virtual

關鍵字,基類的函數将被隐藏(注意别與重載混淆)。

【規則 7-2-3】注意如果派生類的函數與基類的函數同名,并且參數也相同,但是基類函數沒有

virtual 關鍵字。此時,基類的函數被隐藏(注意别與覆寫混淆)。

7.2 内聯函數

【規則 7-3-1】盡量用内聯取代宏代碼,提高函數的執行效率(速度)。

【規則 7-3-2】關鍵字 inline 必須與函數定義體放在一起才能使函數成為内聯,僅将 inline 放在函 數聲明前面不起任何作用。

【規則 7-3-3】如果函數體内的代碼比較長或函數體内出現循環,則不宜使用内聯。

8.  記憶體管理

記憶體配置設定方式有三種:

(1) 從靜态存儲區域配置設定。記憶體在程式編譯的時候就已經配置設定好,這塊記憶體在程式的整個運作期間都 存在。例如全局變量,static  變量。

(2) 在棧上建立。在執行函數時,函數内局部變量的存儲單元都可以在棧上建立,函數執行結束時這 些存儲單元自動被釋放。棧記憶體配置設定運算内置于處理器的指令集中,效率很高,但是配置設定的記憶體容量有 限。

(3) 從堆上配置設定,亦稱動态記憶體配置設定。程式在運作的時候用malloc  或new  申請任意多少的記憶體,程 序員自己負責在何時用free  或delete  釋放記憶體。動态記憶體的生存期由我們決定,使用非常靈活,但問 題也最多。

【規則8-1-1】用malloc 或new 申請記憶體之後,應該立即檢查指針值是否為NULL。防止使用指針值為NULL 的記憶體。

【規則8-1-2】不要忘記為數組和動态記憶體賦初值。防止将未被初始化的記憶體作為右值使用。

【規則8-1-3】避免數組或指針的下标越界,特别要當心發生“多1”或者“少1”操作。

【規則8-1-4】動态記憶體的申請與釋放必須配對,防止記憶體洩漏。

【規則8-1-5】用free  或delete  釋放了記憶體之後,立即将指針設定為NULL,防止産生“野指針”。

9  類的構造函數、析構函數、成員函數與指派函數

9.1 類的構造函數

【規則 9-1-1】“預設的拷貝構造函數”和“預設的指派函數”均采用“位拷貝”而非“值拷貝”的方 式來實作,若類中含有指針變量,不能采用預設的方式。

【規則 9-1-2】如果類存在繼承關系,派生類必須在其初始化表裡調用基類的構造函數。

【規則 9-1-3】類的 const 常量隻能在初始化表裡被初始化,因為它不能在函數體内用指派的方式來初 始化。

【規則 9-1-4】非内部資料類型的成員對象采用初始化表的方式初始化較好。

【規則 9-1-5】拷貝構造函數和指派函數非常容易混淆,常導緻錯寫、錯用。拷貝構造函數是在對象被 建立時調用的,而指派函數隻能被已經存在了的對象調用。

9.2 成員函數

【規則 9-2-1】任何不會修改資料成員的函數都應該聲明為 const 類型。

10.  類的繼承群組合

對于 C++程式而言,設計孤立的類是比較容易的,難的是正确設計基類及其派生類。

【規則 10-1-1】如果類 A 和類 B 毫不相關,不可以為了使 B 的功能更多些而讓 B 繼承 A 的功能和屬 性。

【規則 10-1-2】若在邏輯上 B 是 A 的“一種情況”,則允許 B 繼承 A 的功能和屬性。

【規則 10-1-3】若在邏輯上 A 是 B 的“一部分”(a part of),則不允許 B 從 A 派生,而是要用 A 和其 它東西組合出 B。

11.  其他規範及建議

11.1 提高程式的效率

程式的時間效率是指運作速度,空間效率是指程式占用記憶體或者外存的狀況。全局效率是指站在整

個系統的角度上考慮的效率,局部效率是指站在子產品或函數角度上考慮的效率。

【規則11-1-1】不要一味地追求程式的效率,應當在滿足正确性、可靠性、健壯性、可讀性等品質因素 的前提下,設法提高程式的效率。

【規則11-1-2】以提高程式的全局效率為主,提高局部效率為輔。

【規則11-1-3】在優化程式的效率時,應當先找出限制效率的“瓶頸”,不要在無關緊要之處優化。

【規則11-1-4】先優化資料結構和算法,再優化執行代碼。

【規則11-1-5】有時候時間效率和空間效率可能對立,此時應當分析那個更重要,作出适當的折衷。例 如多花費一些記憶體來提高性能。

【規則11-1-6】不要追求緊湊的代碼,因為緊湊的代碼并不能産生高效的機器碼。

11.2 一些有益的建議

【建議11-2-1】當心那些視覺上不易分辨的操作符發生書寫錯誤。我們經常會把“==”誤寫成“=”,

象“||”、“&&”、“<=”、“>=”這類符号也很容易發生“丢失”失誤。然而編譯器卻不一定能自 動指出這類錯誤。

【建議11-2-2】變量(指針、數組)被建立之後應當及時把它們初始化,以防止把未被初始化的變量當 成右值使用。

【建議11-2-3】當心變量的初值、預設值錯誤,或者精度不夠。

【建議11-2-4】當心資料類型轉換發生錯誤。盡量使用顯式的資料類型轉換,避免讓編譯器輕悄悄地進 行隐式的資料類型轉換。

【建議11-2-5】當心變量發生上溢或下溢,數組的下标越界。

【建議11-2-6】當心忘記編寫錯誤處理程式,當心錯誤處理程式本身有誤。

【建議11-2-7】當心檔案I/O  有錯誤。

【建議11-2-8】避免編寫技巧性很高代碼。

【建議11-2-9】不要設計面面俱到、非常靈活的資料結構。

【建議11-2-10】如果原有的代碼品質比較好,盡量複用它。但是不要修補很差勁的代碼,應當重新編 寫。

【建議11-2-11】盡量使用标準庫函數,不要“發明”已經存在的庫函數。

【建議11-2-12】盡量不要使用與具體硬體或軟體環境關系密切的變量。

【建議11-2-13】把編譯器的選擇項設定為最嚴格狀态。

【建議11-2-14】如果可能的話,使用LogiScope 等工具進行代碼審查。