C/C++代碼規範(一)——文件結構、排版、命名規則

代碼規範是每個程序員倍加關注的問題。C語言編碼與註釋規範,懂不懂編碼規範筆試時能一眼被看出來,有良好的編碼習慣筆試時也會增色不少,知道你有過工作經驗或者參與過大型項目。本文是在學習林銳博士的《高質量C/C++編程指南》一書中,摘錄、總結的筆記。

1 文件結構

每個C++/C程序通常分爲兩個文件。一個文件用於保存程序的聲明(declaration),稱爲頭文件。另一個文件用於保存程序的實現(implementation),稱爲定義(definition)文件。
C++/C程序的頭文件以“.h”爲後綴,C程序的定義文件以“.c”爲後綴,C++程序的定義文件通常以“.cpp”爲後綴(也有一些系統以“.cc”或“.cxx”爲後綴)。

1.1 版權和版本的聲明

版權和版本的聲明位於頭文件和定義文件的開頭,主要內容有:
(1)版權信息。
(2)文件名稱,標識符,摘要。
(3)當前版本號,作者/修改者,完成日期。
(4)版本歷史信息。

示例1-1
/*
* 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這類聲明。

示例1-2
#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-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-2ifforwhiledo等語句自佔一行,執行語句不得緊跟其後。不論執行語句有多少都要加{}。這樣可以防止書寫失誤。
風格良好的代碼行

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】關鍵字之後要留空格。像constvirtualinlinecase 等關鍵字之後至少要留一個空格,否則無法辨析關鍵字。像ifforwhile等關鍵字之後應留一個空格再跟左括號(,以突出關鍵字。
規則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; 從語義上講此寫法比較直觀,即xint類型的指針。上述寫法的弊端是容易引起誤解,例如: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開頭。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章