最近在重溫c++基礎(chǔ)知識(shí),反正閑著也是沒(méi)事干,以前只聽(tīng)說(shuō)過(guò)這本書(shū),但是從來(lái)沒(méi)看過(guò)。畢業(yè)那會(huì)看的是面向?qū)ο缶幊蹋彩菄?guó)外的某個(gè)牛人寫(xiě)的,但是是機(jī)械出版社出版的,讀起來(lái)比較順暢,但是里面有些細(xì)節(jié)還是不清楚。這本書(shū)是c++大牛寫(xiě)的,清華大學(xué)出版社翻譯出來(lái),剛開(kāi)始讀的時(shí)候覺(jué)得翻譯挺別扭的,但是自己去看原文的時(shí)候,又礙于語(yǔ)言學(xué)的不好,發(fā)現(xiàn)很多翻譯出來(lái)還沒(méi)這本書(shū)好,就先姑且讀之,以后慢慢再看英文原版。
讀了這么長(zhǎng)時(shí)間的書(shū)了,第一卷基本上要讀完了,最后四章之前看得比較匆忙,沒(méi)好好理解,現(xiàn)在又重新翻了一遍,很多東西跟我原來(lái)想的完全不一樣,而且這本書(shū)讀了之后可以讓人知道為什么要這樣做,從根本上剖析了做這件事的理由,我覺(jué)得讀了之后挺有益的,就先把讀書(shū)筆記貼出來(lái)。
今天分享一下new 和 delete兩個(gè)操作符,面試的時(shí)候面試官很喜歡問(wèn),new 和 malloc有什么區(qū)別?當(dāng)初只是知道new最重要的是要調(diào)用構(gòu)造函數(shù),而不知道為什么非要調(diào)用構(gòu)造函數(shù)。
先來(lái)看一下如果沒(méi)有new的話,用malloc()怎么在內(nèi)存中分配一塊區(qū)域給一個(gè)類對(duì)象,分配了之后,接下來(lái)應(yīng)該怎么辦。
下面是書(shū)中的一個(gè)例子,解答了上述的問(wèn)題:
class Obj
{
int i,j,k;
enum{ sz = 100 };
char buf[sz];
public:
void initialize()
{
cout << "initializing Obj" << endl;
i = j = k;
memset(buf, 0 , sz);
}
void destroy()const
{
cout<< "destroying Obj" << endl;
}
};
int main()
{
Obj* obj = (Obj*)malloc(sizeof(Obj));
assert(obj != null);
obj->initialize();
obj->destroy();
return 0;
}
我們知道,在c++中,編譯器一定要初始化一個(gè)類對(duì)象之后才能對(duì)其進(jìn)行操作,使用malloc動(dòng)態(tài)分配內(nèi)存給對(duì)象之后,還要記得一定要初始化它,不然,這個(gè)對(duì)象就沒(méi)辦法用,而且很多人只關(guān)注類的功能,而不往往會(huì)忘掉初始化,這是bug的一個(gè)重要來(lái)源。所以c++想把分配內(nèi)存和初始化這兩份工作一塊讓編譯器處理了,不用我們程序員惦記著。
所以new的作用就是,先為這個(gè)對(duì)象分配一塊足夠容納這個(gè)對(duì)象的內(nèi)存,然后調(diào)用其構(gòu)造函數(shù),初始化這塊內(nèi)存區(qū)域(注意分配內(nèi)存和初始化的順序,這對(duì)我們自理解重載new操作符有幫助)。delete的作用就是,先調(diào)用析構(gòu)函數(shù)將內(nèi)存中的參數(shù)清理掉(我理解是:指針清零,其他變量不管), 然后釋放這塊內(nèi)存。
這里說(shuō)一下前幾天看的網(wǎng)易公開(kāi)課中的《編程范式》里面講的,其實(shí)我們分配堆上的內(nèi)存的時(shí)候,哪塊內(nèi)存被分配了,哪塊沒(méi)被分配是記錄在一塊區(qū)域里面的,未被分配的空間都是鏈?zhǔn)竭B接起來(lái)的,被分配的空間也是鏈?zhǔn)竭B接起來(lái)的。第一塊未被分配的空間最后四個(gè)字節(jié)指向了下一個(gè)未被分配的空間的地址,然后依次指向后面的未被分配的空間地址。被分配的空間亦是如此。那么,當(dāng)我們調(diào)用new的時(shí)候,申請(qǐng)的這塊內(nèi)存的首地址就被寫(xiě)到前面一個(gè)的最后四個(gè)字節(jié)里面,說(shuō)明這些空間是被占用的,不能再被分配了。當(dāng)我們調(diào)用delete的時(shí)候,這塊內(nèi)存的首地址就會(huì)被寫(xiě)到未分配的字節(jié)中去。如果我們一塊堆內(nèi)存已經(jīng)沒(méi)有用了,但是沒(méi)有釋放掉,那么它的地址不會(huì)被寫(xiě)到未使用字節(jié)中去,那么你永遠(yuǎn)也用不了這塊內(nèi)存,這就是內(nèi)存泄漏。
那如果我們重載new和delete操作符的時(shí)候,我們不可能顯示地調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù),那我們?cè)趺磥?lái)重載呢?答案是,我們只負(fù)責(zé)分配內(nèi)存的規(guī)則,調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)是編譯器的事情。
如果我們用全局函數(shù)來(lái)重載new的話,那么原來(lái)的new就沒(méi)用了,就完完全全被我們的new覆蓋了。如果用成員函數(shù)來(lái)做的話,new只針對(duì)那個(gè)類。
new操作符會(huì)接受一個(gè)size_t類型的參數(shù),標(biāo)志著要分配的內(nèi)存的大小,這個(gè)參數(shù)是編譯器給我們的。編譯器會(huì)針對(duì)這個(gè)類型判斷該分配多少內(nèi)存。new操作符的返回值是個(gè)void*,因?yàn)槲覀冏龅闹皇且峙湟粔K內(nèi)存,這塊內(nèi)存還沒(méi)被初始化,所以返回值不是指向一個(gè)類的指針。
delete操作符接受一個(gè)指向new操作符分配的內(nèi)存的void*指針,之所以是void*, 而不是類型的指針,是因?yàn)槲覀兊膁elete是在析構(gòu)函數(shù)調(diào)用完之后才開(kāi)始回收空間的。所以我說(shuō)要記住順序啊。
好了,來(lái)點(diǎn)代碼說(shuō)說(shuō)重載是怎么進(jìn)行的吧。
#include <cstdlib> // malloc(), free()
#include <cstdio> // puts() , printf()
using namespace std;
void* operator new( size_t sz)
{
printf("operator new %d Bytes\n", sz);
void* m = malloc(sz);
if(!m) puts("out of memory");
return m;
}
void operator delete(void* m)
{
puts("operator delete");
free(m);
}
class S
{
int i[100];
public:
S(){ puts("S::S()");}
~S(){ puts("S::~S()");}
};
int main()
{
int* p = new int(47);
delete p;
S* s = new S;
delete s;
S* sa = new S[3];// 這里我一直以為會(huì)new 3次,調(diào)用3次構(gòu)造函數(shù)。但實(shí)際上是new 1次,
// 分配1204個(gè)字節(jié),然后三次構(gòu)造函數(shù),多余的4個(gè)字節(jié)存放包含的對(duì)象的數(shù)量信息
delete sa; // 這里同樣的,調(diào)用三次析構(gòu)函數(shù),delete操作符調(diào)用一次。
return 0;
}
注意:構(gòu)造函數(shù)總是在new返回之后調(diào)用,如果new失敗的話,構(gòu)造函數(shù)不會(huì)被調(diào)用,且返回值是0。
另外需要注意的是,void*指針最好不能用delete釋放,編了一下程序
void* a = new Obj(2);
delete a;
這段代碼編譯是成功的,但是運(yùn)行時(shí),既沒(méi)有調(diào)用構(gòu)造函數(shù),也沒(méi)有調(diào)用析構(gòu)函數(shù),而且程序出現(xiàn)假死現(xiàn)象。我分析原因是,當(dāng)編譯器看到第一行代碼的時(shí)候,知道我只是要分配一個(gè)Obj這么大的一塊內(nèi)存空間,因?yàn)槭莢oid* ,所以我也不必為它初始化。在delete的時(shí)候,既然我不知道你這塊內(nèi)存中放的是什么類型的對(duì)象,那我就不知道應(yīng)該調(diào)用哪個(gè)析構(gòu)函數(shù)。情理上這么分析是合情合理的。
另外我學(xué)到的另一點(diǎn)是,重載new[]和重載new操作符原理上是一樣的,它們接收的都是一個(gè)size_t的參數(shù),來(lái)標(biāo)識(shí)分配多少空間。delete[]與delete也基本相同。
大概就是這么多,在堆上分配最大的好處是可以被程序員自己控制,有些時(shí)候,在棧中分配內(nèi)存不能滿足我們的要求,就必須在堆上分配。另外,初始化是c++必須要保證的東西,非常重要。
最后,哪位大神能告訴我怎么排版?博客園自帶的排版功能我怎么不太會(huì)用呢。。。囧
1
posted on 2012-04-20 21:07
Dino-Tech 閱讀(224)
評(píng)論(0) 編輯 收藏 引用