高質量C/C++編程指南筆記



(1)知錯就改;
(2)溫故而知新;
(3)堅持學習,天天向上


應當將修飾符 * 和& 緊靠變量名
註釋的位置應與被描述的代碼相鄰,可以放在代碼的上方或右方,不可放在下方。
當代碼比較長,特別是有多重嵌套時,應當在一些段落的結束處加註釋,便於閱讀。
程序中不要出現標識符完全相同的局部變量和全局變量,儘管兩者的作用域不同而不會發生語法錯誤,但會使人誤解。


假設布爾變量名字爲flag,它與零值比較的標準if 語句如下:
if (flag) // 表示flag 爲真
if (!flag) // 表示flag 爲假


整型變量與零值比較:
應當將整型變量用“==”或“!=”直接與0 比較。
if (value == 0)
if (value != 0)
不可模仿布爾變量的風格而寫成
if (value) // 會讓人誤解 value 是布爾變量
if (!value)


浮點變量與零值比較:


不可將浮點變量用“==”或“!=”與任何數字比較。
千萬要留意,無論是float 還是double 類型的變量,都有精度限制。所以一定要
避免將浮點變量用“==”或“!=”與數字比較,應該設法轉化成“>=”或“<=”形式。
假設浮點變量的名字爲x,應當將
if (x == 0.0) // 隱含錯誤的比較
轉化爲
if ((x>=-EPSINON) && (x<=EPSINON))
其中EPSINON 是允許的誤差(即精度)。


指針變量與零值比較:
應當將指針變量用“==”或“!=”與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)



在多重循環中,如果有可能,應當將最長的循環放在最內層,最短的
循環放在最外層,以減少CPU 跨切循環層的次數。例如示例4-4(b)的效率比示例
4-4(a)的高。


C 語言用 #define 來定義常量(稱
爲宏常量)。C++ 語言除了 #define 外還可以用const 來定義常量(稱爲const 常量)。


有時我們希望某些常量只在類中有效。由於#define 定義的宏常量是全局的,不能
達到目的,於是想當然地覺得應該用const 修飾數據成員來實現。const 數據成員的確
是存在的,但其含義卻不是我們所期望的。const 數據成員只在某個對象生存期內是常
量,而對於整個類而言卻是可變的,因爲類可以創建多個對象,不同的對象其const 數
據成員的值可以不同。
不能在類聲明中初始化const 數據成員。以下用法是錯誤的,因爲類的對象未被創
建時,編譯器不知道SIZE 的值是什麼。
class A
{
const int SIZE = 100; // 錯誤,企圖在類聲明中初始化const 數據成員
int array[SIZE]; // 錯誤,未知的SIZE
};


怎樣才能建立在整個類中都恆定的常量呢?別指望const 數據成員了,應該用類中
的枚舉常量來實現。例如
class A
{
enum { SIZE1 = 100, SIZE2 = 200}; // 枚舉常量
int array1[SIZE1];
int array2[SIZE2];
};

枚舉常量不會佔用對象的存儲空間,它們在編譯時被全部求值。枚舉常量的缺點是:
它的隱含數據類型是整數,其最大值有限,且不能表示浮點數(如PI=3.14159)。



【規則6-1-1】參數的書寫要完整,不要貪圖省事只寫參數的類型而省略參數名字。
如果函數沒有參數,則用void 填充。
例如:
void SetValue(int width, int height); // 良好的風格
void SetValue(int, int); // 不良的風格
float GetValue(void); // 良好的風格
float GetValue(); // 不良的風格


如果參數是指針,且僅作輸入用,則應在類型前加const,以防止該
指針在函數體內被意外修改。

void StringCopy(char *strDestination,const char *strSource);

如果輸入參數以值傳遞的方式傳遞對象,則宜改用“const &”方式
來傳遞,這樣可以省去臨時對象的構造和析構過程,從而提高效率。

不要省略返回值的類型。
C 語言中,凡不加類型說明的函數,一律自動按整型處理。這樣做不會有什麼好處,
卻容易被誤解爲void 類型。
C++語言有很嚴格的類型安全檢查,不允許上述情況發生。由於C++程序可以調用
C 函數,爲了避免混亂,規定任何C++/ C 函數都必須有類型。如果函數沒有返回值,
那麼應聲明爲void 類型。



函數名字與返回值類型在語義上不可衝突。
違反這條規則的典型代表是C 標準庫函數getchar。
例如:
char c;
c = getchar();
if (c == EOF)
?
按照 getchar 名字的意思,將變量c 聲明爲char 類型是很自然的事情。但不幸的是
getchar 的確不是char 類型,而是int 類型,其原型如下:
int getchar(void);
由於c 是char 類型,取值範圍是[-128,127],如果宏EOF 的值在char 的取值範圍
之外,那麼if 語句將總是失敗,這種“危險”人們一般哪裏料得到!導致本例錯誤的責
任並不在用戶,是函數getchar 誤導了使用者。


char *strcpy(char *strDest,const char *strSrc);
strcpy 函數將strSrc 拷貝至輸出參數strDest 中,同時函數的返回值又是strDest。
這樣做並非多此一舉,可以獲得如下靈活性:
char str[20];
int length = strlen( strcpy(str, “Hello World”) );


return 語句不可返回指向“棧內存”的“指針”或者“引用”,因爲該內存在函數
體結束時被自動銷燬


以下是“值傳遞”的示例程序。由於Func1 函數體內的x 是外部變量n 的一份拷貝,
改變x 的值不會影響n, 所以n 的值仍然是0。
void Func1(int x)
{
x = x + 10;
}


int n = 0;
Func1(n);
cout << “n = ” << n << endl; // n = 0


以下是“指針傳遞”的示例程序。由於Func2 函數體內的x 是指向外部變量n 的指
針,改變該指針的內容將導致n 的值改變,所以n 的值成爲10。
void Func2(int *x)
{
(* x) = (* x) + 10;
}
?
int n = 0;
Func2(&n);
cout << “n = ” << n << endl; // n = 10
以下是“引用傳遞”的示例程序。由於Func3 函數體內的x 是外部變量n 的引用,
x 和n 是同一個東西,改變x 等於改變n,所以n 的值成爲10。
void Func3(int &x)
{
x = x + 10;
}
?
int n = 0;
Func3(n);
cout << “n = ” << n << endl; // n = 10
對比上述三個示例程序,會發現“引用傳遞”的性質象“指針傳遞”,而書寫方式
象“值傳遞”。實際上“引用”可以做的任何事情“指針”也都能夠做,爲什麼還要“引
用”這東西?

內存分配方式有三種:
(1) 從靜態存儲區域分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的
整個運行期間都存在。例如全局變量,static 變量。
(2) 在棧上創建。在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函
數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集
中,效率很高,但是分配的內存容量有限。
(3) 從堆上分配,亦稱動態內存分配。程序在運行的時候用malloc 或new 申請任意
多少的內存,程序員自己負責在何時用free 或delete 釋放內存。動態內存的生存
期由我們決定,使用非常靈活,但問題也最多。


常見的內存錯誤及其對策
發生內存錯誤是件非常麻煩的事情。編譯器不能自動發現這些錯誤,通常是在程序
運行時才能捕捉到。而這些錯誤大多沒有明顯的症狀,時隱時現,增加了改錯的難度。
有時用戶怒氣衝衝地把你找來,程序卻沒有發生任何問題,你一走,錯誤又發作了。
常見的內存錯誤及其對策如下:
?? 內存分配未成功,卻使用了它。
編程新手常犯這種錯誤,因爲他們沒有意識到內存分配會不成功。常用解決辦法是,
在使用內存之前檢查指針是否爲NULL。如果指針p 是函數的參數,那麼在函數的入口
處用assert(p!=NULL)進行檢查。如果是用malloc 或new 來申請內存,應該用if(p==NULL)
或if(p!=NULL)進行防錯處理。
?? 內存分配雖然成功,但是尚未初始化就引用它。
犯這種錯誤主要有兩個起因:一是沒有初始化的觀念;二是誤以爲內存的缺省初值
全爲零,導致引用初值錯誤(例如數組)。
內存的缺省初值究竟是什麼並沒有統一的標準,儘管有些時候爲零值,我們寧可信
其無不可信其有。所以無論用何種方式創建數組,都別忘了賦初值,即便是賦零值也不
可省略,不要嫌麻煩。
?? 內存分配成功並且已經初始化,但操作越過了內存的邊界。
例如在使用數組時經常發生下標“多1”或者“少1”的操作。特別是在for 循環語
句中,循環次數很容易搞錯,導致數組操作越界。
?? 忘記了釋放內存,造成內存泄露。
含有這種錯誤的函數每被調用一次就丟失一塊內存。剛開始時系統的內存充足,你
看不到錯誤。終有一次程序突然死掉,系統出現提示:內存耗盡。
動態內存的申請與釋放必須配對,程序中malloc 與free 的使用次數一定要相同,
否則肯定有錯誤(new/delete 同理)。
?? 釋放了內存卻繼續使用它。
有三種情況:
(1)程序中的對象調用關係過於複雜,實在難以搞清楚某個對象究竟是否已經釋放了
內存,此時應該重新設計數據結構,從根本上解決對象管理的混亂局面。
(2)函數的return 語句寫錯了,注意不要返回指向“棧內存”的“指針”或者“引用”,
因爲該內存在函數體結束時被自動銷燬。
(3)使用free 或delete 釋放了內存後,沒有將指針設置爲NULL。導致產生“野指針”。
?? 【規則7-2-1】用malloc 或new 申請內存之後,應該立即檢查指針值是否爲NULL。
防止使用指針值爲NULL 的內存。
?? 【規則7-2-2】不要忘記爲數組和動態內存賦初值。防止將未被初始化的內存作爲右
值使用。
?? 【規則7-2-3】避免數組或指針的下標越界,特別要當心發生“多1”或者“少1”
操作。
?? 【規則7-2-4】動態內存的申請與釋放必須配對,防止內存泄漏。
?? 【規則7-2-5】用free 或delete 釋放了內存之後,立即將指針設置爲NULL,防止
產生“野指針”。


指針與數組的對比
C++/C 程序中,指針和數組在不少地方可以相互替換着用,讓人產生一種錯覺,以
爲兩者是等價的。
數組要麼在靜態存儲區被創建(如全局數組),要麼在棧上被創建。數組名對應着
(而不是指向)一塊內存,其地址與容量在生命期內保持不變,只有數組的內容可以改
變。
指針可以隨時指向任意類型的內存塊,它的特徵是“可變”,所以我們常用指針來操作動態內存。指針遠比數組靈活,但也更危險。
下面以字符串爲例比較指針與數組的特性。

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