引言:《C/C++高質量編程》這本書,很好的給出了一些編程規範,是每一個C/C++程序員都應該認真讀取的一本書。幾次閱讀,幾次忘記,今天把一些關鍵點記錄下來,便於以後繼續學習和參考。建議此書和谷歌C++編碼規範一起使用,相得益彰。
【規則2-2-1】一行代碼只做一件事情,如只定義一個變量,或只寫一條語句。這樣的代碼容易閱讀,並且方便於寫註釋。
【建議2-2-1】儘可能在定義變量的同時初始化該變量(就近原則)
【規則2-3-1】關鍵字之後要留空格。象const、virtual、inline、case 等關鍵字之後至少要留一個空格,否則無法辨析關鍵字。象if、for、while等關鍵字之後應留一個空格再跟左括號‘(’,以突出關鍵字。
【規則2-3-2】函數名之後不要留空格,緊跟左括號‘(’,以與關鍵字區別。
【規則2-5-1】代碼行最大長度宜控制在70至80個字符以內。代碼行不要過長,否則眼睛看不過來,也不便於打印。
【規則2-6-1】應當將修飾符 * 和 & 緊靠變量名
例如:
char *name;
int *x, y; // 此處y不會被誤解爲指針
【規則3-1-6】變量的名字應當使用“名詞”或者“形容詞+名詞”。
例如:
float value;
float oldValue;
float newValue;
【規則3-1-7】全局函數的名字應當使用“動詞”或者“動詞+名詞”(動賓詞組)。類的成員函數應當只使用“動詞”,被省略掉的名詞就是對象本身。
例如:
DrawBox(); // 全局函數
box->Draw(); // 類的成員函數
【建議3-1-1】儘量避免名字中出現數字編號,如Value1,Value2等,除非邏輯上的確需要編號。
【規則3-2-1】類名和函數名用大寫字母開頭的單詞組合而成。
例如:
class Node; // 類名
class LeafNode; // 類名
void Draw(void); // 函數名
void SetValue(int value); // 函數名
【規則3-2-2】變量和參數用小寫字母開頭的單詞組合而成。
例如:
BOOL flag;
int drawMode;
【規則3-2-4】靜態變量加前綴s_(表示static)。【規則3-2-5】如果不得已需要全局變量,則使全局變量加前綴g_(表示global)。
const數據成員只在某個對象生存期內是常量,而對於整個類而言卻是可變的,因爲類可以創建多個對象,不同的對象其const數據成員的值可以不同。
不能在類聲明中初始化const數據成員。
const數據成員的初始化只能在類構造函數的初始化表中進行,例如
class A
{…
A(int size); // 構造函數
const int SIZE ;
};
A::A(int size) : SIZE(size) // 構造函數的初始化表
【建議6-2-2】如果函數的返回值是一個對象,有些場合用“引用傳遞”替換“值傳遞”可以提高效率。
【規則6-3-2】在函數體的“出口處”,對return語句的正確性和效率進行檢查。
如果函數有返回值,那麼函數的“出口處”是return語句。我們不要輕視return語句。如果return語句寫得不好,函數要麼出錯,要麼效率低下。
注意事項如下:
(1)return語句不可返回指向“棧內存”的“指針”或者“引用”,因爲該內存在函數體結束時被自動銷燬。例如
char * Func(void)
{
char str[] = “hello world”; // str的內存位於棧上
…
return str; // 將導致錯誤
}
(2)要搞清楚返回的究竟是“值”、“指針”還是“引用”。
(3)如果函數返回值是一個對象,要考慮return語句的效率。例如
return String(s1 + s2);
這是臨時對象的語法,表示“創建一個臨時對象並返回它”。不要以爲它與“先創建一個局部對象temp並返回它的結果”是等價的,如
String temp(s1 + s2);
return temp;
實質不然,上述代碼將發生三件事。首先,temp對象被創建,同時完成初始化;然後拷貝構造函數把temp拷貝到保存返回值的外部存儲單元中;最後,temp在函數結束時被銷燬(調用析構函數)。然而“創建一個臨時對象並返回它”的過程是不同的,編譯器直接把臨時對象創建並初始化在外部存儲單元中,省去了拷貝和析構的化費,提高了效率。
類似地,我們不要將
return int(x + y); // 創建一個臨時變量並返回它
寫成
int temp = x + y;
return temp;
由於內部數據類型如int,float,double的變量不存在構造函數與析構函數,雖然該“臨時變量的語法”不會提高多少效率,但是程序更加簡潔易讀。
內存分配方式
內存分配方式有三種:
(1)從靜態存儲區域分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。例如全局變量,static變量。
(2)在棧上創建。在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集中,效率很高,但是分配的內存容量有限。
(3)從堆上分配,亦稱動態內存分配。程序在運行的時候用malloc或new申請任意多少的內存,程序員自己負責在何時用free或delete釋放內存。動態內存的生存期由我們決定,使用非常靈活,但問題也最多。
注意不要返回指向“棧內存”的“指針”或者“引用”,因爲該內存在函數體結束時被自動銷燬。
【規則7-2-5】用free或delete釋放了內存之後,立即將指針設置爲NULL,防止產生“野指針”。
內存耗盡怎麼辦?
如果在申請動態內存時找不到足夠大的內存塊,malloc和new將返回NULL指針,宣告內存申請失敗。通常有三種方式處理“內存耗盡”問題。
(1)判斷指針是否爲NULL,如果是則馬上用return語句終止本函數。
(2)判斷指針是否爲NULL,如果是則馬上用exit(1)終止整個程序的運行。(3)爲new和malloc設置異常處理函數。
關鍵字inline必須與函數定義體放在一起才能使函數成爲內聯,僅將inline放在函數聲明前面不起任何作用。
使用const提高函數的健壯性
const常量,const更大的魅力是它可以修飾函數的參數、返回值,甚至函數的定義體。用const修飾函數的參數,const只能修飾輸入參數。
如果輸入參數採用“指針傳遞”,那麼加const修飾可以防止意外地改動該指針,起到保護作用。
如果輸入參數採用“值傳遞”,由於函數將自動產生臨時變量用於複製該參數,該輸入參數本來就無需保護,所以不要加const修飾。
例如不要將函數void Func1(int x) 寫成void Func1(const int x)。同理不要將函數void Func2(A a) 寫成void Func2(const A a)。其中A爲用戶自定義的數據類型。
對於非內部數據類型的輸入參數,應該將“值傳遞”的方式改爲“const引用傳遞”,目的是提高效率。例如將void Func(A a) 改爲void
Func(const A &a)。
對於內部數據類型的輸入參數,不要將“值傳遞”的方式改爲“const引用傳遞”。否則既達不到提高效率的目的,又降低了函數的可理解性。例如void
Func(int x) 不應該改爲void Func(const int &x)。
如果給以“指針傳遞”方式的函數返回值加const修飾,那麼函數返回值(即指針)的內容不能被修改,該返回值只能被賦給加const修飾的同類型指針。
例如函數
const char * GetString(void);
如下語句將出現編譯錯誤:
char *str = GetString();
正確的用法是
const char *str = GetString();
如果函數返回值採用“值傳遞方式”,由於函數會把返回值複製到外部臨時的存儲單元中,加const修飾沒有任何價值。
例如不要把函數int GetInt(void) 寫成const int GetInt(void)。
同理不要把函數A GetA(void) 寫成const A GetA(void),其中A爲用戶自定義的數據類型。
如果返回值不是內部數據類型,將函數A GetA(void) 改寫爲const A & GetA(void)的確能提高效率。但此時千萬千萬要小心,一定要搞清楚函數究竟是想返回一個對象的“拷貝”還是僅返回“別名”就可以了,否則程序會出錯。
函數返回值採用“引用傳遞”的場合並不多,這種方式一般只出現在類的賦值函數中,目的是爲了實現鏈式表達。