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