代碼規範是每個程序員倍加關注的問題。C語言編碼與註釋規範,懂不懂編碼規範筆試時能一眼被看出來,有良好的編碼習慣筆試時也會增色不少,知道你有過工作經驗或者參與過大型項目。本文是在學習林銳博士的《高質量C/C++編程指南》一書中,摘錄、總結的筆記。
文章目錄
1 文件結構
每個C++/C程序通常分爲兩個文件。一個文件用於保存程序的聲明(declaration),稱爲頭文件。另一個文件用於保存程序的實現(implementation),稱爲定義(definition)文件。
C++/C程序的頭文件以“.h”爲後綴,C程序的定義文件以“.c”爲後綴,C++程序的定義文件通常以“.cpp”爲後綴(也有一些系統以“.cc”或“.cxx”爲後綴)。
1.1 版權和版本的聲明
版權和版本的聲明位於頭文件和定義文件的開頭,主要內容有:
(1)版權信息。
(2)文件名稱,標識符,摘要。
(3)當前版本號,作者/修改者,完成日期。
(4)版本歷史信息。
/*
* Copyright (c) 2001,上海貝爾有限公司網絡應用事業部
* All rights reserved.
*
* 文件名稱:filename.h
* 文件標識:見配置管理計劃書
* 摘 要:簡要描述本文件的內容
*
* 當前版本:1.1
* 作 者:輸入作者(或修改者)名字
* 完成日期:2001年7月20日
*
* 取代版本:1.0
* 原作者 :輸入原作者(或修改者)名字
* 完成日期:2001年5月10日
*/
1.2 頭文件的結構
頭文件由三部分內容組成:
(1)頭文件開頭處的版權和版本聲明。
(2)預處理塊。
(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】頭文件中只存放“聲明”而不存放“定義”。
在C++ 語法中,類的成員函數可以在聲明的同時被定義,並且自動成爲內聯函數。這雖然會帶來書寫上的方便,但卻造成了風格不一致,弊大於利。建議將成員函數的定義與聲明分開,不論該函數體有多麼小。
【建議1-2-2】不提倡使用全局變量,儘量不要在頭文件中出現象extern int value
這類聲明。
#ifndef GRAPHICS_H // 防止graphics.h被重複引用
#define GRAPHICS_H
#include <math.h> // 引用標準庫的頭文件
…
#include "myheader.h" // 引用非標準庫的頭文件
…
void Function1(…); // 全局函數聲明
…
class Box // 類結構聲明
{
…
};
#endif
1.3 定義文件的結構
定義文件有三部分內容:
(1) 定義文件開頭處的版權和版本聲明(參見示例1-1)。
(2) 對一些頭文件的引用。
(3) 程序的實現體(包括數據和代碼)。
假設定義文件的名稱爲graphics.cpp
,定義文件的結構參見示例1-3
// 版權和版本聲明見示例1-1,此處省略。
#include "graphics.h" // 引用頭文件
…
// 全局函數的實現體
void Function1(…)
{
…
}
// 類成員函數的實現體
void Box::Draw(…)
{
…
}
1.4 頭文件的作用
早期的編程語言如Basic、Fortran沒有頭文件的概念,C++/C語言的初學者雖然會用使用頭文件,但常常不明其理。這裏對頭文件的作用略作解釋:
(1)通過頭文件來調用庫功能。在很多場合,源代碼不便(或不準)向用戶公佈,只要向用戶提供頭文件和二進制的庫即可。用戶只需要按照頭文件中的接口聲明來調用庫功能,而不必關心接口怎麼實現的。編譯器會從庫中提取相應的代碼。
(2)頭文件能加強類型安全檢查。如果某個接口被實現或被使用時,其方式與頭文件中的聲明不一致,編譯器就會指出錯誤,這一簡單的規則能大大減輕程序員調試、改錯的負擔。
1.5 目錄結構
如果一個軟件的頭文件數目比較多(如超過十個),通常應將頭文件和定義文件分別保存於不同的目錄,以便於維護。
例如可將頭文件保存於include
目錄,將定義文件保存於source
目錄(可以是多級目錄)。
如果某些頭文件是私有的,它不會被用戶的程序直接引用,則沒有必要公開其“聲明”。爲了加強信息隱藏,這些私有的頭文件可以和定義文件存放於同一個目錄。
2 程序的版式
版式雖然不會影響程序的功能,但會影響可讀性。程序的版式追求清晰、美觀,是程序風格的重要構成因素。
可以把程序的版式比喻爲“書法”。好的“書法”可讓人對程序一目瞭然,看得興致勃勃。差的程序“書法”如螃蟹爬行,讓人看得索然無味,更令維護者煩惱有加。
2.1 空行
空行起着分隔程序段落的作用。空行得體(不過多也不過少)將使程序的佈局更加清晰。空行不會浪費內存,雖然打印含有空行的程序是會多消耗一些紙張,但是值得。所以不要捨不得用空行。
【規則2-1-1】在每個類聲明之後、每個函數定義結束之後都要加空行。
// 空行
void Function1(…)
{
…
}
// 空行
void Function2(…)
{
…
}
// 空行
void Function3(…)
{
…
}
【規則2-1-2】在一個函數體內,邏揖上密切相關的語句之間不加空行,其它地方應加空行分隔。
// 空行
while (condition)
{
statement1;
// 空行
if (condition)
{
statement2;
}
else
{
statement3;
}
// 空行
statement4;
}
2.2 代碼行
【規則2-2-1】一行代碼只做一件事情,如只定義一個變量,或只寫一條語句。這樣的代碼容易閱讀,並且方便於寫註釋。
【規則2-2-2】if
、for
、while
、do
等語句自佔一行,執行語句不得緊跟其後。不論執行語句有多少都要加{}。這樣可以防止書寫失誤。
風格良好的代碼行
int width; // 寬度
int height; // 高度
int depth; // 深度
x = a + b;
y = c + d;
z = e + f;
if (width < height)
{
dosomething();
}
for (initialization; condition; update)
{
dosomething();
}
// 空行
other();
風格不良的代碼行(是不是和自己寫的代碼異常相似)
int width, height, depth; // 寬度高度深度
x = a + b; y = c + d; z = e + f;
if (width < height) dosomething();
for (initialization; condition; update)
dosomething();
other();
2.3 代碼行內的空格
【規則2-3-1】關鍵字之後要留空格。像const
、virtual
、inline
、case
等關鍵字之後至少要留一個空格,否則無法辨析關鍵字。像if
、for
、while
等關鍵字之後應留一個空格再跟左括號(
,以突出關鍵字。
【規則2-3-2】函數名之後不要留空格,緊跟左括號(
,以與關鍵字區別。
【規則2-3-3】(
向後緊跟,)
、,
、;
向前緊跟,緊跟處不留空格。如:Function(x, y, z)
和for (initialization; condition; update)
注意此處:;
前都沒有空格,)
也是。不良的分格如:Function( x, y, z )
或者for ( initialization; condition; update )
。
【規則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
}
if (condition)
{
… // program code
}
else
{
… // program code
}
for (initialization; condition; update)
{
… // program code
}
While (condition)
{
… // program code
}
如果出現嵌套的{}
,則使用縮進對齊,如:
{
…
{
…
}
…
}
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.6 修飾符的位置
修飾符 *
和&
應該靠近數據類型還是該靠近變量名,是個有爭議的活題。
若將修飾符 *
靠近數據類型,例如:int* x
; 從語義上講此寫法比較直觀,即x
是int
類型的指針。上述寫法的弊端是容易引起誤解,例如:int* x, y
; 此處y容易被誤解爲指針變量。
【規則2-6-1】應當將修飾符 * 和 & 緊靠變量名
例如:
char *name;
int *x, y; // 此處y不會被誤解爲指針
2.7 註釋
C語言的註釋符爲“/*…*/
”。而在C++語言中,程序塊的註釋常採用“/*…*/
”,行註釋一般採用“//…
”。註釋通常用於:
(1)版本、版權聲明;
(2)函數接口說明;
(3)重要的代碼行或段落提示。
雖然註釋有助於理解代碼,但注意不可過多地使用註釋。
【規則2-7-1】註釋是對代碼的“提示”,而不是文檔。程序中的註釋不可喧賓奪主,註釋太多了會讓人眼花繚亂。註釋的花樣要少。
【規則2-7-2】如果代碼本來就是清楚的,則不必加註釋。否則多此一舉,令人厭煩。例如
i++; // i 加 1,多餘的註釋
【規則2-7-3】邊寫代碼邊註釋,修改代碼同時修改相應的註釋,以保證註釋與代碼的一致性。不再有用的註釋要刪除。
【規則2-7-4】註釋應當準確、易懂,防止註釋有二義性。錯誤的註釋不但無益反而有害。
【規則2-7-5】儘量避免在註釋中使用縮寫,特別是不常用縮寫。
【規則2-7-6】註釋的位置應與被描述的代碼相鄰,可以放在代碼的上方或右方,不可放在下方。
【規則2-7-8】當代碼比較長,特別是有多重嵌套時,應當在一些段落的結束處加註釋,便於閱讀。
3 命名規則
沒有一種命名規則可以讓所有的程序員贊同,程序設計教科書一般都不指定命名規則。命名規則對軟件產品而言並不是“成敗悠關”的事,我們不要化太多精力試圖發明世界上最好的命名規則,而應當制定一種令大多數項目成員滿意的命名規則,並在項目中貫徹實施。
3.1 共性規則
共性規則是被大多數程序員採納的,我們應當在遵循這些共性規則的前提下,再擴充特定的規則。
【規則3-1-1】標識符應當直觀且可以拼讀,可望文知意,不必進行“解碼”。
標識符最好採用英文單詞或其組合,便於記憶和閱讀。切忌使用漢語拼音來命名。程序中的英文單詞一般不會太複雜,用詞應當準確。例如不要把CurrentValue
寫成NowValue
。
【規則3-1-2】標識符的長度應當符合min-length && max-information原則。
幾十年前老ANSI C規定名字不準超過6個字符,現今的C++/C不再有此限制。一般來說,長名字能更好地表達含義,所以函數名、變量名、類名長達十幾個字符不足爲怪。但並不代表名字越長越好:例如變量名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;
float newValue;
【規則3-1-7】全局函數的名字應當使用“動詞”或者“動詞+名詞”(動賓詞組)。類的成員函數應當只使用“動詞”,被省略掉的名詞就是對象本身。
例如:
DrawBox(); // 全局函數
box->Draw(); // 類的成員函數
【規則3-1-8】用正確的反義詞組命名具有互斥意義的變量或相反動作的函數等。
例如:
int minValue;
int maxValue;
int SetValue(…);
int GetValue(…);
【建議3-1-9】儘量避免名字中出現數字編號,如Value1
,Value2
等,除非邏輯上的確需要編號。這是爲了防止程序員偷懶,不肯爲命名動腦筋而導致產生無意義的名字(因爲用數字編號最省事)。
3.2 簡單的Windows應用程序命名規則
林銳博士對“匈牙利”命名規則做了合理的簡化,下述的命名規則簡單易用,比較適合於Windows應用軟件的開發。類Unix也可以作爲一個參考。再加上現在很多程序跨平臺,所以這一套規則不止適用於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; //全局常量MAX
const int MAX_LENGTH = 100; //全局常量MAX_LENGTH
【規則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開頭。