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)版本歷史信息
示例 1-1 版權和版本的聲明
版本標識:採用 <主版本號>.<次版本號>.<修訂號> 來命名自己產品的編號。Linux 核心還有一個約定,
就是如果次版本號是偶數(如0、2、4 等),代表正式版本,如果次版本號是奇數(如1、3、5 等),代
表的是開發過程中的測試版本。修訂號則相當於 Build 號,用來標識一些小的改動。
1.2 頭文件的結構
頭文件由三部分內容組成:
1)頭文件開頭處的版權和版本聲明(參見示例1-1)。
2)預處理塊。
/*
* Copyright (c) 2003,北京梅梅出品有限公司
* All rights reserved.
*
* 文件名稱:filename.h
* 文件標識:見配置管理計劃書
* 摘要:簡要描述本文件的內容
*/
#ifndef GRAPHICS_H // 防止graphics.h 被重複引用
#define GRAPHICS_H
下面其它的聲明代碼
………
下面是原作者、版本、完成、日期和當前版本的信息
/* 當前版本:1.1.2
* 作者:輸入作者(或修改者)名字
* 完成日期:2003年5月20日
*
* 取代版本:1.1.1
* 原作者:輸入原作者(或修改者)名字
* 完成日期:2003年4月10日
*/
編程規範系列


// 版權和版本聲明見示例1-1,此處省略。
#ifndef GRAPHICS_H // 防止graphics.h 被重複引用
#define GRAPHICS_H
#include <math.h> // 引用標準庫的頭文件

#include “myheader.h” // 引用非標準庫的頭文件

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

class Box // 類結構聲明
{ …
};
#endif
// 版權和版本聲明見示例1-1,此處省略。
#include “graphics.h” // 引用頭文件

// 全局函數的實現體
void Function1(… )
{
...
}
// 類成員函數的實現體
void Box::Draw(...)
{
...
}
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-2 C++/C 頭文件的結構
1.3 定義文件的結構
定義文件有三部分內容:
1) 定義文件開頭處的版權和版本聲明(參見示例1-1)。
2) 對一些頭文件的引用。
3) 程序的實現體(包括數據和代碼)。
假設定義文件的名稱爲graphics.cpp,定義文件的結構參見示例1-3
編程規範系列


示例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 語言的註釋符爲“/*? */”。C++語言中,程序塊的註釋常採用“/*? */”,行註釋一般採用
“//? ”。註釋通常用於:
(1)版本、版權聲明;
(2)函數接口說明;
(3)重要的代碼行或段落提示。
【規則2-7-1】註釋是對代碼的“提示”,而不是文檔。程序中的註釋不可喧賓奪主,註釋太多了會讓
人眼花繚亂。
【規則2-7-2】如果代碼本來就是清楚的,則不必加註釋。否則多此一舉,令人厭煩。
【規則2-7-3】邊寫代碼邊註釋,修改代碼同時修改相應的註釋,以保證註釋與代碼的一致性。不再有
用的註釋要刪除。
【規則2-7-4】註釋應當準確、易懂,防止註釋有二義性。錯誤的註釋不但無益反而有害。
【規則2-7-5】儘量避免在註釋中使用縮寫,特別是不常用縮寫。
【規則2-7-6】註釋的位置應與被描述的代碼相鄰,可以放在代碼的上方或右方,不可放在下方。
【規則2-7-8】當代碼比較長,特別是有多重嵌套時,應當在一些段落的結束處加註釋,便於閱讀。
2.8 類的版式
類可以將數據和函數封裝在一起,其中函數表示了類的行爲(或稱服務)。類提供關鍵字public、
protected 和private,分別用於聲明哪些數據和函數是公有的、受保護的或者是私有的。這樣可以達到
信息隱藏的目的,即讓類僅僅公開必須要讓外界知道的內容,而隱藏其它一切內容。
類的版式主要有兩種方式:
(1)將private 類型的數據寫在前面,而將public 類型的函數寫在後面,如示例8-3(a)。採用這種版
式的程序員主張類的設計“以數據爲中心”,重點關注類的內部結構。
(2)將public 類型的函數寫在前面,而將private 類型的數據寫在後面,如示例8.3(b)採用這種版式
的程序員主張類的設計“以行爲爲中心”,重點關注的是類應該提供什麼樣的接口(或服務)。
【建議2-8-1】建議採用“以行爲爲中心”的書寫方式,即首先考慮類應該提供什麼樣的函數。這樣做
不僅讓自己在設計類時思路清晰,而且方便別人閱讀。
編程規範系列

class A
{
private:
int i, j;
float x, y;

public:
void Func1(void);
void Func2(void);

}
class A
{
public:
void Func1(void);
void Func2(void);

private:
int i, j;
float x, y;

}
示例8.3(a) 以數據爲中心版式 示例8.3(b) 以行爲爲中心的版式
3 命名規則
3.1 共性規則
本節論述的共性規則是被大多數程序員採納的,我們應當在遵循這些共性規則的前提下,再擴充特
定的規則,如3.2 節。
命名兩個基本原則:1. 含義清晰,不易混淆; 2. 不和其它模塊、系統API的命名空間相沖突即可。
【規則3-1-1】標識符應當直觀且可以拼讀,可望文知意,不必進行“解碼”。
標識符最好採用英文單詞或其組合,便於記憶和閱讀。切忌使用漢語拼音來命名。程序中的英文
單詞一般不會太複雜,用詞應當準確。例如不要把CurrentValue 寫成NowValue 。
【規則3-1-2】標識符的長度應當符合“min-length && max-information”原則。
一般來說,長名字能更好地表達含義,所以函數名、變量名、類名長達十幾個字符不足爲怪。但名
字並不是越長約好! 例如變量名maxval 就比maxValueUntilOverflow好用。單字符的名字也是有用的,
常見的如i,j,k,m,n,x,y,z 等,它們通常可用作函數內的局部變量。
【規則3-1-3】命名規則儘量與所採用的操作系統或開發工具的風格保持一致。
例如Windows 應用程序的標識符通常採用“大小寫”混排的方式,如AddChild。而Unix 應用程序
的標識符通常採用“小寫加下劃線”的方式,如add_child。別把這兩類風格混在一起用。
【規則3-1-4】程序中不要出現僅靠大小寫區分的相似的標識符。
例如:int x, X; // 變量x 與X 容易混淆
void foo(int x); // 函數foo 與FOO 容易混淆
void FOO(float x);
【規則3-1-5】程序中不要出現標識符完全相同的局部變量和全局變量,儘管兩者的作用域不同而不會
發生語法錯誤,但會使人誤解。
【規則3-1-6】變量的名字應當使用“名詞”或者“形容詞+名詞”。
例如:float value;
float oldValue;
【規則3-1-7】全局函數的名字應當使用“動詞”或者“動詞+名詞”(動賓詞組)。類的成員函數應
當只使用“動詞”,被省略掉的名詞就是對象本身。
例如: DrawBox(); // 全局函數
編程規範系列

box->Draw(); // 類的成員函數
【規則3-1-8】用正確的反義詞組命名具有互斥意義的變量或相反動作的函數等。
例如:int minValue;
int maxValue;
int SetValue(? );
int GetValue(? );
【建議3-1-1】儘量避免名字中出現數字編號,如Value1,Value2 等,除非邏輯上的確需要編號。
3.2 Windows 應用程序命名規則
【規則3-2-1】類名和函數名用大寫字母開頭的單詞組合而成。
例如:class Node; // 類名
class LeafNode; // 類名
void Draw(void); // 函數名
void SetValue(int value); // 函數名
【規則3-2-2】變量和參數用小寫字母開頭的單詞組合而成。
例如:BOOL flag;
int drawMode;
【規則3-2-3】常量全用大寫的字母,用下劃線分割單詞。
例如:const int MAX = 100;
const int MAX_LENGTH = 100;
【規則3-2-4】靜態變量加前綴s_(表示static)。
例如:void Init(?)
{
static int s_initValue; // 靜態變量
?
}
【規則3-2-5】如果需要定義全局變量,則使全局變量加前綴g_(表示global)。
例如:int g_howManyPeople; // 全局變量
int g_howMuchMoney; // 全局變量
【規則3-2-6】類的數據成員加前綴m_(表示member),這樣可以避免數據成員與成員函數的參數同名。
例如:void Object::SetValue(int width, int height)
{
m_width = width;
m_height = height;
}
【規則3-2-7】爲了防止某一軟件庫中的一些標識符和其它軟件庫中的衝突,可以爲各種標識符加上能
反映軟件性質的前綴。例如三維圖形標準OpenGL 的所有庫函數均以gl 開頭,所有常量(或宏定義)均
以GL 開頭。
3.3 Linux 應用程序函數命名規則
函數命名應遵循下面兩個原則:
1)屬於某一模塊的函數,加上前綴,前綴爲模塊縮寫;
編程規範系列

2)函數名應該表明函數意義,格式爲"前綴_名詞_動詞";
4. 表達式和基本語句
4.1 運算符的優先級
【規則4-1-1】如果代碼行中的運算符比較多,用括號確定表達式的操作順序,避免使用默認的優先級。
爲了防止產生歧義並提高可讀性,應當用括號確定表達式的操作順序。
例如:word = (high << 8) | low
if ((a | b) && (a & c))
4.2 複合表達式
如a = b = c = 0 這樣的表達式稱爲複合表達式。允許複合表達式存在的理由是:(1)書寫簡潔;
(2)可以提高編譯效率。但要防止濫用複合表達式。
【規則4-2-1】不要編寫太複雜的複合表達式。
例如:i = a >= b && c < d && c + f <= g + h ; // 複合表達式過於複雜
【規則4-2-2】不要有多用途的複合表達式。
例如:d = (a = b + c) + r ; 該表達式既求a 值又求d 值。
應該拆分爲兩個獨立的語句:a = b + c;
d = a + r;
【規則4-2-3】不要把程序中的複合表達式與“真正的數學表達式”混淆。
例如: if (a < b < c) // a < b < c 是數學表達式而不是程序表達式
並不表示 if ((a<b) && (b<c))
而是成了令人費解的 if ( (a<b)<c )
4.3 if 語句
if 語句是C++/C 語言中最簡單、最常用的語句,然而很多程序員用隱含錯誤的方式寫if 語句。本
節以“與零值比較”爲例,展開討論。
4.3.1 布爾變量與零值比較
【規則4-3-1】不可將布爾變量直接與TRUE、FALSE 或者1、0 進行比較。
根據布爾類型的語義,零值爲“假”(記爲FALSE),任何非零值都是“真”(記爲TRUE)。
TRUE 的值究竟是什麼並沒有統一的標準。例如Visual C++ 將TRUE 定義爲1,而Visual Basic 則將
TRUE 定義爲-1。
假設布爾變量名字爲flag,它與零值比較的標準if 語句如下:
if (flag) // 表示flag 爲真
if (!flag) // 表示flag 爲假
其它的用法都屬於不良風格,例如:
if (flag == TRUE)
if (flag == 1 )
if (flag == FALSE)
if (flag == 0)
編程規範系列

4.3.2 整型變量與零值比較
【規則4-3-2】應當將整型變量用“==”或“!=”直接與0 比較。
假設整型變量的名字爲value,它與零值比較的標準if 語句如下:
if (value == 0)
if (value != 0)
不可模仿布爾變量的風格而寫成
if (value) // 會讓人誤解value 是布爾變量
if (!value)
4.3.3 浮點變量與零值比較
【規則4-3-3】不可將浮點變量用“==”或“!=”與任何數字比較。
千萬要留意,無論是float 還是double 類型的變量,都有精度限制。所以一定要避免將浮點變量
用“==”或“!=”與數字比較,應該設法轉化成“>=”或“<=”形式。
假設浮點變量的名字爲x,應當將
if (x == 0.0) // 隱含錯誤的比較
轉化爲
if ((x>=-EPSINON) && (x<=EPSINON))
其中EPSINON 是允許的誤差(即精度)。
4.3.4 指針變量與零值比較
【規則4-3-4】應當將指針變量用“==”或“!=”與NULL 比較。
指針變量的零值是“空”(記爲NULL)。儘管NULL 的值與0 相同,但是兩者意義不同。假設指針
變量的名字爲p,它與零值比較的標準if 語句如下:
if (p == NULL) // p 與NULL 顯式比較,強調p 是指針變量
if (p != NULL)
不要寫成
if (p == 0) // 容易讓人誤解p 是整型變量
if (p != 0)
或者
if (p) // 容易讓人誤解p 是布爾變量
if (!p)
4.4 循環語句的效率
C++/C 循環語句中,for 語句使用頻率最高,while 語句其次,do 語句很少用。本節重點論述循
環體的效率。提高循環體效率的基本辦法是降低循環體的複雜性。
【建議4-4-1】在多重循環中,如果有可能,應當將最長的循環放在最內層,最短的循環放在最外層,
以減少CPU 跨切循環層的次數。例如示例4-4(b)的效率比示例4-4(a)的高。
編程規範系列

for (row=0; row<100; row++)
{
for ( col=0; col<5; col++ )
{
sum = sum + a[row][col];
}
}
for (col=0; col<5; col++ )
{
for (row=0; row<100; row++)
{
sum = sum + a[row][col];
}
}
示例4-4(a) 低效率:長循環在最外層 示例4-4(b) 高效率:長循環在最內層
【建議4-4-2】如果循環體內存在邏輯判斷,並且循環次數很大,宜將邏輯判斷移到循環體的外面。示
例4-4(c)的程序比示例4-4(d)多執行了N-1 次邏輯判斷。並且由於前者總要進行邏輯判斷,打斷了循環
“流水線”作業,使得編譯器不能對循環進行優化處理,降低了效率。如果N 非常大,最好採用示例4-4(d)
的寫法,可以提高效率。如果N 非常小,兩者效率差別並不明顯,採用示例4-4(c)的寫法比較好,因
爲程序更加簡潔。
for (i=0; i<N; i++)
{
if (condition)
DoSomething();
else
DoOtherthing();
}
if (condition)
{
for (i=0; i<N; i++)
DoSomething();
}
else
{
for (i=0; i<N; i++)
DoOtherthing();
}
表4-4(c) 效率低但程序簡潔 表4-4(d) 效率高但程序不簡潔
4.5 for 語句的循環控制變量
【規則4-5-1】不可在for 循環體內修改循環變量,防止for 循環失去控制。
【建議4-5-1】建議for 語句的循環控制變量的取值採用“半開半閉區間”寫法。
示例4-5(a)中的x 值屬於半開半閉區間“0 =< x < N”,起點到終點的間隔爲N,循環次數爲N。
示例4-5(b)中的x 值屬於閉區間“0 =< x <= N-1”,起點到終點的間隔爲N-1,循環次數爲N。
相比之下,示例4-5(a)的寫法更加直觀,儘管兩者的功能是相同的。
for (int x=0; x<N; x++)
{
...
}
for (int x=0; x<=N-1; x++)
{
...
}
示例4-5(a) 循環變量屬於半開半閉區間 示例4-5(b) 循環變量屬於閉區間
4.6 switch 語句
【規則4-6-1】每個case 語句的結尾不要忘了加break,否則將導致多個分支重疊(除非有意使多個分
支重疊)。
編程規範系列

【規則4-6-2】不要忘記最後那個default 分支。即使程序真的不需要default 處理,也應該保留語句
default : break;
4.7 goto 語句
自從提倡結構化設計以來,goto 就成了有爭議的語句。首先,由於goto 語句可以靈活跳轉,如果
不加限制,它的確會破壞結構化設計風格。其次,goto 語句經常帶來錯誤或隱患。它可能跳過了某些
對象的構造、變量的初始化、重要的計算等語句,例如:
goto state;
String s1, s2; // 被goto 跳過
int sum = 0; // 被goto 跳過
...
state:
...
如果編譯器不能發覺此類錯誤,每用一次goto 語句都可能留下隱患。
很多人建議廢除C++/C 的goto 語句,以絕後患。但實事求是地說,錯誤是程序員自己造成的,不是
goto 的過錯。goto 語句至少有一處可顯神通,它能從多重循環體中咻地一下子跳到外面,用不着寫很
多次的break 語句。就象樓房着火了,來不及從樓梯一級一級往下走,可從窗口跳出火坑。所以我們主
張少用、慎用goto 語句,而不是完全禁用。
5. 常量
常量是一種標識符,它的值在運行期間恆定不變。C 語言用#define 來定義常量(稱爲宏常量)。
C++ 語言除了#define 外還可以用const 來定義常量(稱爲const 常量)。
5.1 const 與#define 的比較
C++ 語言可以用const 來定義常量,也可以用#define 來定義常量。但是前者比後者有更多的優點:
(1) const 常量有數據類型,而宏常量沒有數據類型。編譯器可以對前者進行類型安全檢查。而對後
者只進行字符替換,沒有類型安全檢查,並且在字符替換可能會
產生意料不到的錯誤(邊際效應)。
(2) 有些集成化的調試工具可以對const 常量進行調試,但是不能對宏常量進行調試。
【規則5-1-1】儘量使用含義直觀的常量來表示那些將在程序中多次出現的數字或字符串。
例如: #define MAX 100 /* 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. 函數設計
一個函數的註釋信息如下例:
/**********************************************************************************
* Function: calculate The area of rectangle *
* parameter: the Length and Width of rectangle *
* outout: the area of rectangle *
***********************************************************************************/
int GetValue(int iLength,int iWidth)
{
……..
return iArea;
}
/*
Error:
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 等工具進行代碼審查。

發佈了23 篇原創文章 · 獲贊 9 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章