這段文字是我從林銳博士的<高質量C/C++編程>節(jié)選出來的片段,使其便于快速閱讀
【規(guī)則1-2-1】為了防止頭文件被重復引用,應當用ifndef/define/endif結構產生預處理塊。
l 【規(guī)則1-2-2】用 #include <filename.h> 格式來引用標準庫的頭文件(編譯器將從標準庫目錄開始搜索)。
l 【規(guī)則1-2-3】用 #include “filename.h” 格式來引用非標準庫的頭文件(編譯器將從用戶的工作目錄開始搜索)。
2 【建議1-2-1】頭文件中只存放“聲明”而不存放“定義
--------------------------------
4.3.1 布爾變量與零值比較
l 【規(guī)則4-3-1】不可將布爾變量直接與TRUE、FALSE或者1、0進行比較。
根據(jù)布爾類型的語義,零值為“假”(記為FALSE),任何非零值都是“真”(記為TRUE)。TRUE的值究竟是什么并沒有統(tǒng)一的標準。例如Visual C++ 將TRUE定義為1,而Visual Basic則將TRUE定義為-1。
假設布爾變量名字為flag,它與零值比較的標準if語句如下:
if (flag) // 表示flag為真
if (!flag) // 表示flag為假
其它的用法都屬于不良風格,例如:
4.3.2 整型變量與零值比較
l 【規(guī)則4-3-2】應當將整型變量用“==”或“!=”直接與0比較。
假設整型變量的名字為value,它與零值比較的標準if語句如下:
if (value == 0)
if (value != 0)
4.3.3 浮點變量與零值比較
l 【規(guī)則4-3-3】不可將浮點變量用“==”或“!=”與任何數(shù)字比較。
千萬要留意,無論是float還是double類型的變量,都有精度限制。所以一定要避免將浮點變量用“==”或“!=”與數(shù)字比較,應該設法轉化成“>=”或“<=”形式。
假設浮點變量的名字為x,應當將
if (x == 0.0) // 隱含錯誤的比較
轉化為
if ((x>=-EPSINON) && (x<=EPSINON))
其中EPSINON是允許的誤差(即精度)。
4.3.4 指針變量與零值比較
l 【規(guī)則4-3-4】應當將指針變量用“==”或“!=”與NULL比較。
指針變量的零值是“空”(記為NULL)。盡管NULL的值與0相同,但是兩者意義不同。假設指針變量的名字為p,它與零值比較的標準if語句如下:
if (p == NULL) // p與NULL顯式比較,強調p是指針變量
if (p != NULL)
-------------------------
循環(huán)語句的效率
--------------------------
const數(shù)據(jù)成員的初始化只能在類構造函數(shù)的初始化表中進行,例如
class A
{…
A(int size); // 構造函數(shù)
const int SIZE ;
};
A::A(int size) : SIZE(size) // 構造函數(shù)的初始化表
{
…
}
A a(100); // 對象 a 的SIZE值為100
A b(200); // 對象 b 的SIZE值為200
---------------------------------
規(guī)則6-1-3】如果參數(shù)是指針,且僅作輸入用,則應在類型前加const,以防止該指針在函數(shù)體內被意外修改。
例如:
void StringCopy(char *strDestination,const char *strSource);
【規(guī)則6-1-4】如果輸入參數(shù)以值傳遞的方式傳遞對象,則宜改用“const &”方式來傳遞,這樣可以省去臨時對象的構造和析構過程,從而提高效率。
-------------------------------------
7.1內存分配方式
內存分配方式有三種:
(1) 從靜態(tài)存儲區(qū)域分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。例如全局變量,static變量。
(2) 在棧上創(chuàng)建。在執(zhí)行函數(shù)時,函數(shù)內局部變量的存儲單元都可以在棧上創(chuàng)建,函數(shù)執(zhí)行結束時這些存儲單元自動被釋放。棧內存分配運算內置于處理器的指令集中,效率很高,但是分配的內存容量有限。
(3) 從堆上分配,亦稱動態(tài)內存分配。程序在運行的時候用malloc或new申請任意多少的內存,程序員自己負責在何時用free或delete釋放內存。動態(tài)內存的生存期由我們決定,使用非常靈活,但問題也最多。
--------------------------------------
u 內存分配未成功,卻使用了它。
編程新手常犯這種錯誤,因為他們沒有意識到內存分配會不成功。常用解決辦法是,在使用內存之前檢查指針是否為NULL。如果指針p是函數(shù)的參數(shù),那么在函數(shù)的入口處用assert(p!=NULL)進行檢查。如果是用malloc或new來申請內存,應該用if(p==NULL) 或if(p!=NULL)進行防錯處理。
------------------------------
C++/C程序中,指針和數(shù)組在不少地方可以相互替換著用,讓人產生一種錯覺,以為兩者是等價的。
數(shù)組要么在靜態(tài)存儲區(qū)被創(chuàng)建(如全局數(shù)組),要么在棧上被創(chuàng)建。數(shù)組名對應著(而不是指向)一塊內存,其地址與容量在生命期內保持不變,只有數(shù)組的內容可以改變。
指針可以隨時指向任意類型的內存塊,它的特征是“可變”,所以我們常用指針來操作動態(tài)內存。指針遠比數(shù)組靈活,但也更危險
示例7-3-1中,字符數(shù)組a的容量是6個字符,其內容為hello\0。a的內容可以改變,如a[0]= ‘X’。指針p指向常量字符串“world”(位于靜態(tài)存儲區(qū),內容為world\0),常量字符串的內容是不可以被修改的。從語法上看,編譯器并不覺得語句p[0]= ‘X’有什么不妥,但是該語句企圖修改常量字符串的內容而導致運行錯誤。
7.3.2 內容復制與比較
不能對數(shù)組名進行直接復制與比較。示例7-3-2中,若想把數(shù)組a的內容復制給數(shù)組b,不能用語句 b = a ,否則將產生編譯錯誤。應該用標準庫函數(shù)strcpy進行復制。同理,比較b和a的內容是否相同,不能用if(b==a) 來判斷,應該用標準庫函數(shù)strcmp進行比較。
語句p = a 并不能把a的內容復制指針p,而是把a的地址賦給了p。要想復制a的內容,可以先用庫函數(shù)malloc為p申請一塊容量為strlen(a)+1個字符的內存,再用strcpy進行字符串復制。同理,語句if(p==a) 比較的不是內容而是地址,應該用庫函數(shù)strcmp來比較。
// 數(shù)組…
char a[] = "hello";
char b[10];
strcpy(b, a); // 不能用 b = a;
if(strcmp(b, a) == 0) // 不能用 if (b == a)
…
// 指針…
int len = strlen(a);
char *p = (char *)malloc(sizeof(char)*(len+1));
strcpy(p,a); // 不要用 p = a;
if(strcmp(p, a) == 0) // 不要用 if (p == a)
------------------------------------
針p指向a,但是sizeof(p)的值卻是4。這是因為sizeof(p)得到的是一個指針變量的字節(jié)數(shù),相當于sizeof(char*),而不是p所指的內存容量
void Func(char a[100])
{
cout<< sizeof(a) << endl; // 4字節(jié)而不是100字節(jié)
}
-----------------------------------
毛病出在函數(shù)GetMemory中。編譯器總是要為函數(shù)的每個參數(shù)制作臨時副本,指針參數(shù)p的副本是 _p,編譯器使 _p = p。如果函數(shù)體內的程序修改了_p的內容,就導致參數(shù)p的內容作相應的修改。這就是指針可以用作輸出參數(shù)的原因。在本例中,_p申請了新的內存,只是把_p所指的內存地址改變了,但是p絲毫未變。所以函數(shù)GetMemory并不能輸出任何東西。事實上,每執(zhí)行一次GetMemory就會泄露一塊內存,因為沒有用free釋放內存
-----------------------------------
用函數(shù)返回值來傳遞動態(tài)內存這種方法雖然好用,但是常常有人把return語句用錯了。這里強調不要用return語句返回指向“棧內存”的指針,因為該內存在函數(shù)結束時自動消亡,見示例7-4-4。
char *GetString(void)
{
char p[] = "hello world";
return p; // 編譯器將提出警告
}
-------------------------------------
用調試器跟蹤示例7-5,發(fā)現(xiàn)指針p被free以后其地址仍然不變(非NULL),只是該地址對應的內存是垃圾,p成了“野指針”。如果此時不把p設置為NULL,會讓人誤以為p是個合法的指針。
-------------------------------
alloc與free是C++/C語言的標準庫函數(shù),new/delete是C++的運算符。它們都可用于申請動態(tài)內存和釋放內存。
對于非內部數(shù)據(jù)類型的對象而言,光用maloc/free無法滿足動態(tài)對象的要求。對象在創(chuàng)建的同時要自動執(zhí)行構造函數(shù),對象在消亡之前要自動執(zhí)行析構函數(shù)。由于malloc/free是庫函數(shù)而不是運算符,不在編譯器控制權限之內,不能夠把執(zhí)行構造函數(shù)和析構函數(shù)的任務強加于malloc/free。
函數(shù)malloc的原型如下:
void * malloc(size_t size);
用malloc申請一塊長度為length的整數(shù)類型的內存,程序如下:
int *p = (int *) malloc(sizeof(int) * length);
我們應當把注意力集中在兩個要素上:“類型轉換”和“sizeof”。
u malloc返回值的類型是void *,所以在調用malloc時要顯式地進行類型轉換,將void * 轉換成所需要的指針類型。
u malloc函數(shù)本身并不識別要申請的內存是什么類型,它只關心內存的總字節(jié)數(shù)。我們通常記不住int, float等數(shù)據(jù)類型的變量的確切字節(jié)數(shù)。例如int變量在16位系統(tǒng)下是2個字節(jié),在32位下是4個字節(jié);而float變量在16位系統(tǒng)下是4個字節(jié),在32位下也是4個字節(jié)。
free(p)能正確地釋放內存。如果p是NULL指針,那么free對p無論操作多少次都不會出問題。如果p不是NULL指針,那么free對p連續(xù)操作兩次就會導致程序運行錯誤。
-----------------------------------
運算符new使用起來要比函數(shù)malloc簡單得多,例如:
int *p1 = (int *)malloc(sizeof(int) * length);
int *p2 = new int[length];
這是因為new內置了sizeof、類型轉換和類型安全檢查功能。對于非內部數(shù)據(jù)類型的對象而言,new在創(chuàng)建動態(tài)對象的同時完成了初始化工作。如果對象有多個構造函數(shù),那么new的語句也可以有多種形式。例如
在用delete釋放對象數(shù)組時,留意不要丟了符號‘[]’。例如
delete []objects; // 正確的用法
delete objects; // 錯誤的用法
后者相當于delete objects[0],漏掉了另外99個對象。
------------------------------------------
如果C++程序要調用已經被編譯后的C函數(shù),該怎么辦?
假設某個C函數(shù)的聲明如下:
void foo(int x, int y);
該函數(shù)被C編譯器編譯后在庫中的名字為_foo,而C++編譯器則會產生像_foo_int_int之類的名字用來支持函數(shù)重載和類型安全連接。由于編譯后的名字不同,C++程序不能直接調用C函數(shù)。C++提供了一個C連接交換指定符號extern“C”來解決這個問題。例如:
extern “C”
{
void foo(int x, int y);
… // 其它函數(shù)
}
或者寫成
extern “C”
{
#include “myheader.h”
… // 其它C頭文件
}
這就告訴C++編譯譯器,函數(shù)foo是個C連接,應該到庫中找名字_foo而不是找_foo_int_int。C++編譯器開發(fā)商已經對C標準庫的頭文件作了extern“C”處理,所以我們可以用#include 直接引用這些頭文件。
----------------------------------------------
對于任意一個類A,如果不想編寫上述函數(shù),C++編譯器將自動為A產生四個缺省的函數(shù),如
A(void); // 缺省的無參數(shù)構造函數(shù)
A(const A &a); // 缺省的拷貝構造函數(shù)
~A(void); // 缺省的析構函數(shù)
A & operate =(const A &a); // 缺省的賦值函數(shù)
-----------------------------------------------
構造從類層次的最根處開始,在每一層中,首先調用基類的構造函數(shù),然后調用成員對象的構造函數(shù)。析構則嚴格按照與構造相反的次序執(zhí)行,該次序是唯一的,否則編譯器將無法自動執(zhí)行析構過程。
一個有趣的現(xiàn)象是,成員對象初始化的次序完全不受它們在初始化表中次序的影響,只由成員對象在類中聲明的次序決定。這是因為類的聲明是唯一的,而類的構造函數(shù)可以有多個,因此會有多個不同次序的初始化表。如果成員對象按照初始化表的次序進行構造,這將導致析構函數(shù)無法得到唯一的逆序
------------------------------------------------
u 如果輸入參數(shù)采用“指針傳遞”,那么加const修飾可以防止意外地改動該指針,起到保護作用。
例如StringCopy函數(shù):
void StringCopy(char *strDestination, const char *strSource);
對于非內部數(shù)據(jù)類型的輸入參數(shù),應該將“值傳遞”的方式改為“const引用傳遞”,目的是提高效率。例如將void Func(A a) 改為void Func(const A &a)。
對于內部數(shù)據(jù)類型的輸入參數(shù),不要將“值傳遞”的方式改為“const引用傳遞”。否則既達不到提高效率的目的,又降低了函數(shù)的可理解性。例如void Func(int x) 不應該改為void Func(const int &x)。
--------------------------------------------------
u 如果給以“指針傳遞”方式的函數(shù)返回值加const修飾,那么函數(shù)返回值(即指針)的內容不能被修改,該返回值只能被賦給加const修飾的同類型指針。
例如函數(shù)
const char * GetString(void);
如下語句將出現(xiàn)編譯錯誤:
char *str = GetString();
正確的用法是
const char *str = GetString();
u 如果函數(shù)返回值采用“值傳遞方式”,由于函數(shù)會把返回值復制到外部臨時的存儲單元中,加const修飾沒有任何價值。
例如不要把函數(shù)int GetInt(void) 寫成const int GetInt(void)。
同理不要把函數(shù)A GetA(void) 寫成const A GetA(void),其中A為用戶自定義的數(shù)據(jù)類型
----------------------------------------------------
任何不會修改數(shù)據(jù)成員的函數(shù)都應該聲明為const類型。如果在編寫const成員函數(shù)時,不慎修改了數(shù)據(jù)成員,或者調用了其它非const成員函數(shù),編譯器將指出錯誤,這無疑會提高程序的健壯性。
以下程序中,類stack的成員函數(shù)GetCount僅用于計數(shù),從邏輯上講GetCount應當為const函數(shù)。編譯器將指出GetCount函數(shù)中的錯誤。
class Stack
{
public:
void Push(int elem);
int Pop(void);
int GetCount(void) const; // const成員函數(shù)
private:
int m_num;
int m_data[100];
};
int Stack::GetCount(void) const
{
++ m_num; // 編譯錯誤,企圖修改數(shù)據(jù)成員m_num
Pop(); // 編譯錯誤,企圖調用非const函數(shù)
return m_num;
}
const成員函數(shù)的聲明看起來怪怪的:const關鍵字只能放在函數(shù)聲明的尾部,大概是因為其它地方都已經被占用了。
----------------------------------------------
#ifndef GRAPHICS_H // 防止graphics.h被重復定義
#define GRAPHICS_H
#include <math.h>// 引用標準庫的頭文件…
#include “myheader.h” // 引用非標準庫的頭文件…
void Function1(…); // 全局函數(shù)聲明…
class Box // 類結構聲明{…};
#endif