首先,強烈推薦林銳博士這本《高品質C++/C 編程指南》,請花一兩個小時認真閱讀這本百頁經書,你將會獲益匪淺。草草看過,個人收穫記錄如下。
標頭檔的作用略作解釋:
(1)通過標頭檔來調用庫功能。在很多場合,原始碼不便(或不準)向使用者公布,只要向使用者提供標頭檔和二進位的庫即可。使用者只需要按照標頭檔中的介面聲明來調用庫功能,而不必關心介面怎麼實現的。編譯器會從庫中提取相應的代碼。
(2)標頭檔能加強型別安全檢查。如果某個介面被實現或被使用時,其方式與標頭檔中的聲明不一致,編譯器就會指出錯誤,這一簡單的規則能大大減輕程式員調試、改錯的負擔。
另:extern可以置於變數或者函數前,以標示變數或者函數的定義在別的檔案中,提示編譯器遇到此變數和函數時在其他模組中尋找其定義。如果函數的聲明中帶有關鍵字extern,僅僅是暗示這個函數可能在別的源檔案裡定義,沒有其它作用。
空行
a.在每個類聲明之後、每個函數定義結束之後都要加空行。
b.在一個函數體內,邏揖上密切相關的語句之間不加空行,其它地方應加空行分隔。(這一條值得注意)
程式碼
a.一行代碼只做一件事情,如只定義一個變數,或唯寫一條語句。這樣的代碼容易閱讀,並且方便於寫注釋。
b.if、for、while、do 等語句自佔一行,執行語句不得緊跟其後。不論執行語句有多少都要加{}。這樣可以防止書寫失誤。
for(initalization;contition;update){ dosomething();}//空行other();
c.儘可能在定義變數的同時初始化該變數(就近原則)
程式碼內的空格
a.關鍵字後要留空格,如const、virtual、inline、case以及if、for、while;函數名之後不要留空格,緊跟左括弧‘(’,以與關鍵字區別。
b.逗號與分號之後要留空格;“=”、“+=” “>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等二元操作符的前後應當加空格;一元操作符前後不加空格。
c.象“[]”、“.”、“->”這類操作符前後不加空格。
d.對於運算式比較長的for 語句和if 語句,為了緊湊起見可以適當地去掉一些空格。
for (i=0; i<10; i++) // 良好的風格
長行拆分
程式碼最大長度宜控制在70 至80 個字元以內。長運算式要在低優先順序操作符處拆分成新行,操作符放在新行之首(以便突出操作符)。拆分出的新行要進行適當的縮排,使排版整齊,語句可讀。
if ((very_longer_variable1 >= very_longer_variable12) && (very_longer_variable3 <= very_longer_variable14) && (very_longer_variable5 <= very_longer_variable16)){ dosomething();}
注釋
a.注釋是對代碼的“提示”,而不是文檔。程式中的注釋不可喧賓奪主,注釋太多了會讓人眼花繚亂。注釋的花樣要少。
b.邊寫代碼邊注釋,修改代碼同時修改相應的注釋,以保證注釋與代碼的一致性。不再有用的注釋要刪除。
c.盡量避免在注釋中使用縮寫,特別是不常用縮寫。
d.注釋的位置應與被描述的代碼相鄰,可以放在代碼的上方或右方,不可放在下方。
f.當代碼比較長,特別是有多重嵌套時,應當在一些段落的結束處加註釋,便於閱讀。
類的版式
類的版式主要有兩種方式:
(1)將private 類型的資料寫在前面,而將public 類型的函數寫在後面。採用這種版式的程式員主張類的設計“以資料為中心”,重點關注類的內部結構。
(2)將public 類型的函數寫在前面,而將private 類型的資料寫在後面。採用這種版式的程式員主張類的設計“以行為為中心”,重點關注的是類應該提供什麼樣的介面(或服務)。
建議讀者採用“以行為為中心”的書寫方式,即首先考慮類應該提供什麼樣的函數。
class A{ public: void Func1(void); void Func2(void); … private: int i, j; float x, y; …}
共性規則
a.標識符應當直觀且可以拼讀,可望文知意;標識符的長度應當符合“min-length && max-information”原則。
b.命名規則盡量與所採用的作業系統或開發工具的風格保持一致。
例如 Windows 應用程式的標識符通常採用“大小寫”混排的方式,如AddChild。而Unix 應用程式的標識符通常採用“小寫加底線”的方式,如add_child。別把這兩類風格混在一起使用。
c.變數的名字應當使用“名詞”或者“形容詞+名詞”;全域函數的名字應當使用“動詞”或者“動詞+名詞”(動賓片語)。
d.盡量避免名字中出現數字編號,如Value1,Value2 等,除非邏輯上的確需要編號。這是為了防止程式員偷懶,不肯為命名動腦筋而導致產生無意義的名字(因為用數字編號最省事)。
簡單的Windows 應用程式命名規則
a.類名和函數名用大寫字母開頭的單片語合而成
b.變數和參數用小寫字母開頭的單片語合而成。
c.常量全用大寫的字母,用底線分割單詞。
d.靜態變數加首碼s_(表示static)。
e.如果不得已需要全域變數,則使全域變數加首碼g_(表示global)。
f.類的資料成員加首碼m_(表示member),這樣可以避免資料成員與成員函數的參數同名。
g.為了防止某一軟體庫中的一些標識符和其它軟體庫中的衝突,可以為各種標識符加上能反映軟體性質的首碼。
if語句
不可將浮點變數用“==”或“!=”與任何數字比較。
if ((x >= -EPSINON) && (x <= EPSINON))//其中EPSINON 是允許的誤差(即精度)。
有時候我們可能會看到 if (NULL == p) 這樣古怪的格式。不是程式寫錯了,是程式員為了防止將 if (p == NULL) 誤寫成 if (p = NULL),而有意把p 和NULL 顛倒。編譯器認為 if (p = NULL) 是合法的,但是會指出 if (NULL = p)是錯誤的,因為NULL不能被賦值。
程式中有時會遇到 if/else/return 的組合,應該將如下不良風格的程式
if (condition) return x;return y;
改寫為
if (condition){ return x;}else{ return y;}
或者改寫成更加簡練的
return (condition ? x : y);
迴圈語句的效率
C++/C 迴圈語句中,for 語句使用頻率最高,while 語句其次,do 語句很少用。提高迴圈體效率的基本辦法是降低迴圈體的複雜性。
在多重迴圈中,如果有可能,應當將最長的迴圈放在最內層,最短的迴圈放在最外層,以減少CPU 跨切迴圈層的次數。
switch 語句
不要忘記最後那個default 分支。即使程式真的不需要default 處理,也應該保留語句 default : break; 這樣做並非多此一舉,而是為了防止別人誤以為你忘了default 處理。
goto 語句
我們主張少用、慎用goto 語句,而不是禁用。
const 與 #define 的比較
C++ 語言可以用const 來定義常量,也可以用 #define 來定義常量。但是前者比後者有更多的優點:
(1)const 常量有資料類型,而宏常量沒有資料類型。編譯器可以對前者進行型別安全檢查。而對後者只進行字元替換,沒有型別安全檢查,並且在字元替換可能會產生意料不到的錯誤(邊際效應)。
(2)有些整合化的調試工具可以對const 常量進行調試,但是不能對宏常量進行調試。
建議:在C++ 程式中只使用const 常量而不使用宏常量。
類中的常量
有時我們希望某些常量只在類中有效。由於#define 定義的宏常量是全域的,不能達到目的,於是想當然地覺得應該用const 修飾資料成員來實現。const 資料成員的確是存在的,但其含義卻不是我們所期望的。const 資料成員只在某個物件存留期內是常量,而對於整個類而言卻是可變的,因為類可以建立多個對象,不同的對象其const 資料成員的值可以不同。不能在類聲明中初始化const 資料成員。以下用法是錯誤的,因為類的對象未被建立時,編譯器不知道SIZE 的值是什麼。
class A{ … const int SIZE = 100; // 錯誤,企圖在類聲明中初始化const 資料成員 int array[SIZE]; // 錯誤,未知的SIZE};
const 資料成員的初始化只能在類建構函式的初始化表中進行,例如
class A{ … A(int size); // 建構函式 const int SIZE ;};A::A(int size) : SIZE(size) // 建構函式的初始化表{ …}A a(100); // 對象 a 的SIZE 值為100A b(200); // 對象 b 的SIZE 值為200
怎樣才能建立在整個類中都恒定的常量呢?別指望const 資料成員了,應該用類中的枚舉常量來實現。例如
class A{ … enum { SIZE1 = 100, SIZE2 = 200}; // 枚舉常量 int array1[SIZE1]; int array2[SIZE2];};
枚舉常量不會佔用對象的儲存空間,它們在編譯時間被全部求值。枚舉常量的缺點是:它的隱含資料類型是整數,其最大值有限,且不能表示浮點數(如PI=3.14159)。