版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
第3章對(duì)象的創(chuàng)建和銷毀3.1對(duì)象的創(chuàng)建
3.2析構(gòu)函數(shù)3.3new和delete用于對(duì)象本章小結(jié)習(xí)題
在第2章中,我們學(xué)習(xí)了自定義類型(類)的概念和訪問控制,封裝和訪問控制在改進(jìn)庫的易用性方面取得了很大進(jìn)展。類這個(gè)新的自定義的數(shù)據(jù)類型在某些方面比內(nèi)置的類型要好,C++對(duì)這些自定義類型提供類型檢查,從而在使用這些類型時(shí)保證了一定的安全性。在這里,安全性包含初始化和后續(xù)的清除兩部分。對(duì)象的創(chuàng)建和銷毀包含其中。本章將詳細(xì)介紹自定義類型如何通過構(gòu)造函數(shù)確保初始化、用析構(gòu)函數(shù)確保清除,從而確保了一定的安全性。
有時(shí)我們確切地知道程序中使用的變量和對(duì)象等的個(gè)數(shù),但情況并不總是這樣。C語言提供了庫函數(shù)malloc()和free()來確保內(nèi)存在堆空間的申請(qǐng)和釋放,但這不是編譯器所能控制的。C++則提供了兩個(gè)操作符new和delete來確??臻g的申請(qǐng)和釋放,本章我們將學(xué)習(xí)如何使用new和delete來出色地完成這個(gè)任務(wù)。3.1對(duì)象的創(chuàng)建本節(jié)將講述當(dāng)對(duì)象被創(chuàng)建時(shí)需要的幾種特殊的成員函數(shù)——構(gòu)造函數(shù)、默認(rèn)構(gòu)造函數(shù)及拷貝構(gòu)造函數(shù)。當(dāng)創(chuàng)建對(duì)象時(shí)編譯器將自動(dòng)調(diào)用構(gòu)造函數(shù)來初始化對(duì)象。初始化列表提供了一種方便而高效的初始化方式。當(dāng)創(chuàng)建對(duì)象而又不提供參數(shù)時(shí),就需要默認(rèn)構(gòu)造函數(shù)。而用一個(gè)已創(chuàng)建好的對(duì)象來構(gòu)造一個(gè)新的對(duì)象時(shí),拷貝構(gòu)造就派上了用場(chǎng)。3.1.1構(gòu)造函數(shù)字符串在解決實(shí)際問題時(shí)有廣泛的應(yīng)用。下面為自定義的一個(gè)字符串類String:classString{public:init(intlen,char*c); //初始化函數(shù)
intlength(); //返回串的長(zhǎng)度
private: intcurLen; //串的長(zhǎng)度
char*ch; //串存放空間指針
};
當(dāng)創(chuàng)建String的對(duì)象時(shí)必須調(diào)用init()函數(shù)來初始化對(duì)象。但是用init()函數(shù)來初始化對(duì)象容易出錯(cuò)。因?yàn)槌绦騿T在使用該類時(shí)可能忘記調(diào)用init()函數(shù)或者調(diào)用了多次,這樣對(duì)象沒有被初始化或者被初始化多次,程序會(huì)出錯(cuò),且此類錯(cuò)誤很難被發(fā)現(xiàn)。Stroustrup在設(shè)計(jì)C++語言時(shí)充分考慮了這個(gè)問題并很好地予以解決,給出了一種特殊的成員函數(shù),只需要程序員定義,編譯器在適當(dāng)?shù)臅r(shí)候會(huì)自動(dòng)調(diào)用而不需要程序員顯式調(diào)用。此函數(shù)就是構(gòu)造函數(shù)。這樣,上面的String類可以這樣寫:classString{public: String(intlen,char*c); //構(gòu)造函數(shù)
intlength(); //返回串的長(zhǎng)度
private: intcurLen; //串的長(zhǎng)度
char*ch; //串存放數(shù)組
};
構(gòu)造函數(shù)是一個(gè)與類的名字一樣的特殊的成員函數(shù)。構(gòu)造函數(shù)不能被指定返回類型,即使是void也不行。例如這樣寫:
intString(intlen,char*c);則編譯器會(huì)報(bào)錯(cuò)。將上述語句改為:
voidString(intlen,char*c);這樣也會(huì)報(bào)錯(cuò),因?yàn)関oid也是一種返回類型。構(gòu)造函數(shù)不能被指定任何返回類型。構(gòu)造函數(shù)與其他函數(shù)一樣可以有零個(gè)或多個(gè)參數(shù),這樣可以為類定義多個(gè)構(gòu)造函數(shù)。如可以增加以下兩個(gè)構(gòu)造函數(shù):
String(); //沒有參數(shù)的構(gòu)造函數(shù)
String(char*c); //帶一個(gè)參數(shù)的構(gòu)造函數(shù)一般多種構(gòu)造函數(shù)可便于用戶用多種方式來構(gòu)建對(duì)象,可以根據(jù)情況選擇合適的構(gòu)造函數(shù)來創(chuàng)建需要的對(duì)象。如可以這樣寫:char*s="hello!";Strings1(s); //帶一個(gè)參數(shù)的構(gòu)造函數(shù)
Strings2(7,s); //帶兩個(gè)參數(shù)的構(gòu)造函數(shù)都構(gòu)建了內(nèi)容為“hello!”?的字符串對(duì)象。當(dāng)創(chuàng)建對(duì)象時(shí),編譯器會(huì)自動(dòng)調(diào)用構(gòu)造函數(shù)。而構(gòu)造函數(shù)到底何時(shí)被調(diào)用則取決于這個(gè)對(duì)象是局部的還是全局的。如果是局部的,在遇到對(duì)象說明語句時(shí)構(gòu)造函數(shù)就會(huì)被調(diào)用;如果是全局的,在程序開始也就是在main()函數(shù)執(zhí)行之前就會(huì)調(diào)用構(gòu)造函數(shù)。【程序3.1】#include<iostream>usingnamespacestd;//類AclassA{public: A() {cout<<"constuctorA"<<endl;} //A的構(gòu)造函數(shù)
};//類BclassB{public: B() {cout<<"constuctorB"<<endl;} //B的構(gòu)造函數(shù)
};Bb; //全局對(duì)象
intmain(){ cout<<"inmain\n";Aa; //局部對(duì)象
return0;}
運(yùn)行結(jié)果如下:
這說明在進(jìn)入main()函數(shù)之前B的構(gòu)造函數(shù)就被調(diào)用了,在進(jìn)入main()函數(shù)后執(zhí)行到“Aa;”時(shí)A的構(gòu)造函數(shù)被調(diào)用。也可以這樣寫:
String*p=newString(s);這樣,String的構(gòu)造函數(shù)也會(huì)被調(diào)用。不過該對(duì)象是在堆(自由空間)上創(chuàng)建的。這將在后面的章節(jié)中講述。3.1.2初始化參數(shù)列表構(gòu)造函數(shù)與其他函數(shù)一樣,有一個(gè)名字、參數(shù)列表和一個(gè)函數(shù)體。但是它有一點(diǎn)是其他函數(shù)所沒有的,即它還初始化參數(shù)列表(也叫初始化列表)。初始化列表是跟在構(gòu)造函數(shù)參數(shù)列表后面且以冒號(hào)(:)開始,由一些成員變量和括號(hào)括起來的初始化值組成的。其成員變量與成員變量之間以逗號(hào)分隔。如自定義的String類的構(gòu)造函數(shù)可以這樣寫:
String(intlen,char*c):curLen(len),ch(c){//其他成員變量的初始化}
在初始化列表中只初始化了一個(gè)成員變量。如果想給類增加一個(gè)成員變量maxLen,表示最大字符串長(zhǎng)度,這樣構(gòu)造函數(shù)可以有三個(gè)參數(shù),且構(gòu)造函數(shù)可以這樣寫:
String(intlen,intmLen,char*c):curLen(len),ch(c),maxLen(mLen){//其他成員變量的初始化}這些成員變量之間以逗號(hào)分隔。所以類可以定義成這樣:
classString{public: String(intlen,char*c); String(intlen,intmLen,char*c); intlength();private: intcurLen; //串的長(zhǎng)度
intmaxLen; //串的最大長(zhǎng)度
char*ch; //串存放空間指針
};String::String(intlen,char*c):curLen(len),ch(c){ //其他成員變量的初始化
}String::String(intlen,intmLen,char*c):curLen(len),ch(c),maxLen(mLen){ //其他成員變量的初始化
}
注意初始化列表的位置,它只在構(gòu)造函數(shù)定義時(shí)指定,而不能在構(gòu)造函數(shù)聲明時(shí)指定。構(gòu)造函數(shù)也可以這樣定義:
String::String(intlen,char*c){ curLen=len; //其他成員變量的初始化
}
初始化列表的位置是在參數(shù)列表后面、函數(shù)體之前。這說明該表里的初始化工作發(fā)生在函數(shù)體內(nèi)的任何代碼被執(zhí)行之前,也就是說有些成員變量必須在初始化列表中初始化,如常量成員變量、引用類型成員變量、沒有默認(rèn)構(gòu)造函數(shù)的類成員變量(默認(rèn)構(gòu)造將在下一節(jié)中講解)。如下面的構(gòu)造函數(shù)是錯(cuò)誤的:
classConstRef{public: ConstRef(intii);private: inti; constintci; int&ri;};//沒有初始化列表,錯(cuò)誤:ri沒有被初始化
ConstRef::ConstRef(intii){//對(duì)成員變量進(jìn)行賦值
i=ii; //可以
ci=ii; //錯(cuò)誤:不能給常量賦值
ri=i; //給沒有初始化的ri賦值
}
注意:能初始化常量但是不能給常量賦值,而引用一旦被創(chuàng)建必須被初始化。所以在進(jìn)入函數(shù)體之前常量和引用必須已被初始化。初始化它們的唯一地方就是初始化列表。因此,正確的構(gòu)造函數(shù)是:
ConstRef::ConstRef(intii):i(ii),ci(i),ri(ii){}
初始化列表只指定用給定值去初始化成員變量,而沒有指定它們的初始化順序。其初始化順序是類定義時(shí)成員變量的順序。如程序3.2?!境绦?.2】classX{ inti; intj;public: //運(yùn)行時(shí)錯(cuò)誤:j在i的前面被初始化
X(intval):j(val),i(j){} voidprint(){cout<<"i="<<i<<"j="<<j<<endl;}//打印i和j的值
};intmain(){ Xx(2); x.print();return0;}
運(yùn)行結(jié)果如下:
這不是希望得到的結(jié)果。希望得到i和j都是2。但是i是一個(gè)未被初始化的值。因?yàn)樵诙x類時(shí),i在j的前面,雖然在初始化列表中j在i之前,但是i還是在j前面被初始化。而i是用j初始化的,此時(shí)j是未被初始化的,所以i是一個(gè)未被初始化的值。此后j被初始化為2。這樣寫就正確了:
X(intval):j(val),i(val){}
用構(gòu)造函數(shù)列表來初始化成員變量,不僅能初始化不能在函數(shù)體中初始化的成員變量,而且也是一種高效的初始化方式。這在后面的例子中將會(huì)看到。3.1.3默認(rèn)構(gòu)造函數(shù)可以給String類加一個(gè)這樣的構(gòu)造函數(shù):
String(){};
這樣的構(gòu)造函數(shù)就叫做默認(rèn)構(gòu)造函數(shù)。這樣的構(gòu)造函數(shù)沒有給定參數(shù)列表和初始化列表。默認(rèn)構(gòu)造函數(shù)可以有參數(shù),不過它的參數(shù)都得有默認(rèn)值。例如:
String(intlen=10,intmLen=20){};
這也是默認(rèn)構(gòu)造函數(shù)。如果定義類時(shí)沒有定義構(gòu)造函數(shù),那么編譯器會(huì)生成一個(gè)默認(rèn)函數(shù)。如果定義的類為:classString{ intlength();private: intcurLen; //串的長(zhǎng)度
intmaxLen; //串的最大長(zhǎng)度
char*ch; //串存放空間指針
};
這樣創(chuàng)建一個(gè)對(duì)象:
Strings1;
編譯器會(huì)自動(dòng)生成一個(gè)默認(rèn)的構(gòu)造函數(shù),并自動(dòng)調(diào)用該構(gòu)造函數(shù)來構(gòu)建需要的String對(duì)象s1。構(gòu)造函數(shù)的功能就是初始化,那么默認(rèn)構(gòu)造是怎樣初始化成員的呢?默認(rèn)的意思就是編譯器會(huì)有一套規(guī)則,即當(dāng)成員變量是類類型時(shí),會(huì)調(diào)用該類成員所屬類的默認(rèn)構(gòu)造來初始化該成員;當(dāng)成員變量是內(nèi)置數(shù)據(jù)類型(如int、double、指針等)或復(fù)合類型(如數(shù)組)時(shí),它們?nèi)蕴幱谖幢怀跏蓟臓顟B(tài)。但是當(dāng)對(duì)象被定義為全局時(shí),成員變量會(huì)被初始化。下面來看一個(gè)例子?!境绦?.3】#include<iostream>usingnamespacestd;classString{public: intlength(); intcurLen; //串的長(zhǎng)度
intmaxLen; //串的最大長(zhǎng)度
char*ch; //串存放數(shù)組
};intmain(){ Strings2; cout<<"串的長(zhǎng)度:"<<s2.curLen<<endl; cout<<"串的最大長(zhǎng)度:"<<s2.maxLen<<endl; cout<<"存放串的指針:"<<(int)s2.ch<<endl; return0;}
程序的第一行和第二行包含了輸入/輸出流,使用了名字空間(見第8章)。倒數(shù)第二行將指針轉(zhuǎn)為int型輸出。沒有為這個(gè)String類定義構(gòu)造函數(shù),所以編譯器會(huì)自動(dòng)生成一個(gè)默認(rèn)構(gòu)造函數(shù)。為了測(cè)試默認(rèn)構(gòu)造是怎樣初始化的,將成員變量定義為公有。
運(yùn)行結(jié)果如下:
這說明int、指針都處于未被初始化的狀態(tài)。所以當(dāng)類有內(nèi)置數(shù)據(jù)類型和復(fù)合類型時(shí),需自己定義構(gòu)造函數(shù)來初始化它們。否則使用一個(gè)未被初始化的變量時(shí),程序常常會(huì)出錯(cuò)。但是只要定義一個(gè)構(gòu)造函數(shù),包括拷貝構(gòu)造函數(shù)(下一節(jié)將介紹),編譯器將不會(huì)生成默認(rèn)構(gòu)造函數(shù)。那么當(dāng)類沒有默認(rèn)構(gòu)造函數(shù)時(shí),會(huì)帶來什么不便呢?
以下為String類定義一個(gè)構(gòu)造函數(shù):
classString{ public: String(intlen); intlength();private: intcurLen; //串的長(zhǎng)度
char*ch; //串存放數(shù)組
};
現(xiàn)在定義一個(gè)Person類:
classPerson{ Stringname; Stringaddress; //... Person(constString&n,constString&a);};
顯然,String類有一個(gè)構(gòu)造函數(shù),所以編譯器不會(huì)為String類生成默認(rèn)構(gòu)造函數(shù)。當(dāng)定義Person類的構(gòu)造函數(shù)時(shí),必須調(diào)用“String(intlen);”這個(gè)構(gòu)造函數(shù)來初始化成員變量name和address,或者使用拷貝構(gòu)造函數(shù)來初始化。例如:Person::Person(constString&n,constString&a):name(5),address(10) /*這里還存在一個(gè)隱式的類型轉(zhuǎn)換問題。如果加入explicit關(guān)鍵字則會(huì)出錯(cuò),而不是調(diào)用構(gòu)造函數(shù)了*/{}
上面是采用顯示調(diào)用String(intlen)來初始化。下面調(diào)用拷貝構(gòu)造函數(shù)來初始化。
Person::Person(constString&n,constString&a):name(n),address(a){}
而不能這樣寫:
Person::Person(constString&n,constString&a){ name=n; address=a;}
因?yàn)檫@樣賦值,首先需要調(diào)用默認(rèn)構(gòu)造函數(shù)(而String現(xiàn)在沒有默認(rèn)構(gòu)造)產(chǎn)生name和address,然后調(diào)用賦值操作符將n和a的值賦給name和address。順便提一下,就算String有默認(rèn)構(gòu)造函數(shù),這樣定義構(gòu)造函數(shù),效率也沒有使用參數(shù)列表(上面例子中的顯示調(diào)用帶參構(gòu)造函數(shù)和拷貝構(gòu)造函數(shù))高。因?yàn)樯厦嫘枰{(diào)用兩次函數(shù)來完成初始化工作,而初始化列表方式只需要調(diào)用一次函數(shù)即可完成初始化工作(用初始化列表也要調(diào)用兩次拷貝構(gòu)造函數(shù),而在函數(shù)體里初始化則是調(diào)用了默認(rèn)的構(gòu)造函數(shù))。如果一個(gè)類有類成員變量,且該類成員變量所屬類沒有默認(rèn)構(gòu)造函數(shù),則編譯器也不會(huì)為這個(gè)類生成默認(rèn)構(gòu)造函數(shù)。例如:
classPerson{ Stringname; Stringaddress;};
按理說,編譯器會(huì)自動(dòng)為Person生成默認(rèn)構(gòu)造函數(shù)。于是可以這樣寫:
Personp1;但是這樣編譯器會(huì)報(bào)錯(cuò),錯(cuò)誤為Person沒有合適的默認(rèn)構(gòu)造函數(shù)。實(shí)際上編譯器沒有為Person生成默認(rèn)構(gòu)造函數(shù),這是因?yàn)镾tring沒有默認(rèn)構(gòu)造的緣故。沒有默認(rèn)構(gòu)造函數(shù)的類類型無法創(chuàng)建動(dòng)態(tài)數(shù)組。例如:
String*ps=newString[2];
這樣寫編譯器會(huì)報(bào)錯(cuò)。new將在3.3節(jié)講解。
創(chuàng)建靜態(tài)數(shù)組時(shí),必須顯示指定參數(shù)列表。例如:
Stringa[2]={String(10),String(20)};直接這樣寫:
Stringa[2]編譯器也會(huì)報(bào)錯(cuò)。綜上所述,系統(tǒng)生成的默認(rèn)構(gòu)造函數(shù)對(duì)一些成員變量不會(huì)初始化,而沒有默認(rèn)構(gòu)造函數(shù)又會(huì)帶來許多不便。所以定義類時(shí),最好自己定義無參缺省構(gòu)造函數(shù),這樣即使系統(tǒng)不會(huì)生成默認(rèn)構(gòu)造函數(shù)也有了無參構(gòu)造函數(shù),一舉兩得。3.1.4拷貝構(gòu)造函數(shù)拷貝構(gòu)造函數(shù)也是個(gè)“構(gòu)造函數(shù)”,它是用來做初始化工作的。然而它與一般構(gòu)造函數(shù)又有區(qū)別。拷貝構(gòu)造函數(shù)的名字還是和類的名字一樣,其形式參數(shù)有一個(gè)是本類型的一個(gè)引用變量。它與默認(rèn)構(gòu)造一樣,被編譯器隱式地調(diào)用。例如,給String定義一個(gè)拷貝構(gòu)造函數(shù):
String(constString&s);
在下列情況下需要用到拷貝構(gòu)造函數(shù):
(1)當(dāng)用一個(gè)已創(chuàng)建好的對(duì)象來初始化需要的對(duì)象時(shí)。
(2)當(dāng)把對(duì)象作為參數(shù)(參數(shù)不為引用類型)傳遞時(shí)。
(3)當(dāng)把對(duì)象作為返回值時(shí)。
(4)當(dāng)定義類類型數(shù)組時(shí)。
下面分別給出實(shí)例:
#include<iostream>usingnamespacestd;classString{ public: String(); String(constString&s);private: intcurLen; //串的長(zhǎng)度
intmaxLen; char*ch; //串存放數(shù)組
};String::String(){ curLen=0; maxLen=256; ch=NULL; cout<<"默認(rèn)構(gòu)造!"<<endl;}String::String(constString&s){ cout<<"拷貝構(gòu)造!"<<endl;}intmain(){Strings1; Strings2=s1; Strings3(s1);return0;}
上面的程序簡(jiǎn)單地實(shí)現(xiàn)了默認(rèn)構(gòu)造和拷貝構(gòu)造。運(yùn)行結(jié)果如下:
這說明當(dāng)創(chuàng)建s1時(shí)調(diào)用的是默認(rèn)構(gòu)造函數(shù);當(dāng)創(chuàng)建s2時(shí)想用創(chuàng)建好的s1來創(chuàng)建s2,這時(shí)調(diào)用的是拷貝構(gòu)造函數(shù)。同樣s3也是調(diào)用拷貝構(gòu)造函數(shù)來創(chuàng)建的。再給String添加一個(gè)函數(shù):
StringString::getString(Strings){ cout<<"返回傳過來的字符串!"<<endl; returns;}
該函數(shù)的參數(shù)是String,返回值也是String。實(shí)現(xiàn)也很簡(jiǎn)單,即先打印一句話,然后將傳過來的String對(duì)象返回。
將main函數(shù)改為:
intmain(){ Strings1; s1.getString(s1);return0;}
運(yùn)行結(jié)果如下:
同樣s1是由默認(rèn)構(gòu)造函數(shù)創(chuàng)建的。調(diào)用getString輸出了兩句“拷貝構(gòu)造!”,這說明調(diào)用了兩次拷貝構(gòu)造。第一次是因?yàn)閰?shù)是對(duì)象,當(dāng)把s1作為參數(shù)傳遞時(shí),編譯器會(huì)以s1為參數(shù)自動(dòng)調(diào)用拷貝構(gòu)造函數(shù)初始化形參s。第二次是因?yàn)椤皉eturns;”這一句,這也會(huì)使編譯器以s為參數(shù)自動(dòng)調(diào)用拷貝構(gòu)造函數(shù)創(chuàng)建一個(gè)臨時(shí)對(duì)象并返回。下面來看看最后一種情況。當(dāng)定義對(duì)象數(shù)組時(shí):
Stringa[2];這樣編譯器會(huì)自動(dòng)調(diào)用默認(rèn)構(gòu)造函數(shù)兩次。但是怎樣使數(shù)組的元素不一樣呢?可以這樣(上節(jié)的一個(gè)例子):Stringa[2]={String(10),String(20)};String(int)的簡(jiǎn)單實(shí)現(xiàn):
String::String(intlen){ cout<<"帶參構(gòu)造!"<<endl;}
運(yùn)行結(jié)果如下:
這說明在創(chuàng)建數(shù)組時(shí),可調(diào)用相應(yīng)的構(gòu)造函數(shù)來創(chuàng)建對(duì)象,然后調(diào)用拷貝構(gòu)造函數(shù)將創(chuàng)建好的對(duì)象拷到數(shù)組的相應(yīng)元素中(注意:具體的調(diào)用跟編譯器的實(shí)現(xiàn)相關(guān))。如將對(duì)象String(10)拷到a[0],將String(20)拷到a[1]。既然構(gòu)造函數(shù)有默認(rèn)構(gòu)造函數(shù),那么有沒有默認(rèn)的拷貝構(gòu)造函數(shù)呢?有的。如果不定義拷貝構(gòu)造函數(shù),編譯器會(huì)自動(dòng)生成一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù)。但是與默認(rèn)構(gòu)造不同的是,如果定義了其他的構(gòu)造函數(shù)(只要不是拷貝構(gòu)造),編譯器還是會(huì)生成默認(rèn)拷貝構(gòu)造的。
默認(rèn)的拷貝構(gòu)造函數(shù)做的工作是“位拷貝”(也叫淺拷貝),即把對(duì)象對(duì)應(yīng)在內(nèi)存的每一位拷貝到另一個(gè)對(duì)象的內(nèi)存區(qū)域中。當(dāng)類的數(shù)據(jù)成員是內(nèi)置數(shù)據(jù)類型時(shí)(指針除外),默認(rèn)拷貝構(gòu)造函數(shù)是能勝任的。但是當(dāng)類中涉及到內(nèi)存的資源時(shí)(如有一指針指向一片在堆上分配的空間),編譯器生成的默認(rèn)拷貝構(gòu)造函數(shù)就不能勝任了。下面仍以String為例:
classString{ public: String(); String(intlen,char*src); char*getString()const; voidsetString(char*str);private: intcurLen; //串的長(zhǎng)度
intmaxLen; char*ch; //串存放數(shù)組
};String的一個(gè)構(gòu)造函數(shù)實(shí)現(xiàn)如下:
String::String(intlen,char*src){ ch=(char*)malloc(len); curLen=len; strcpy(ch,src); cout<<"帶參構(gòu)造!"<<endl;}malloc函數(shù)是在堆上申請(qǐng)空間,參數(shù)為申請(qǐng)的字節(jié)大小。strcpy函數(shù)是將src的字符串內(nèi)容復(fù)制到ch指向的空間中。另外再添加兩個(gè)成員函數(shù),實(shí)現(xiàn)分別如下:
char*String::getString()const{ returnch;}voidString::setString(char*str){ strcpy(ch,str);}main函數(shù)改為:
intmain(){ char*s="hello!"; Strings1(strlen(s),s); Strings2=s1; //調(diào)用拷貝構(gòu)造函數(shù)初始化
cout<<"s1:"<<s1.getString()<<endl; cout<<"s2:"<<s2.getString()<<endl; s2.setString("haha!"); cout<<"Afterchange---s1:"<<s1.getString()<<endl; cout<<"Afterchange---s2:"<<s2.getString()<<endl;return0;}
函數(shù)strlen是得到字符串的長(zhǎng)度。程序先調(diào)用構(gòu)造函數(shù)String(int,char*)創(chuàng)建s1為hello!,然后編譯器會(huì)調(diào)用自動(dòng)生成的拷貝構(gòu)造函數(shù)初始化s2。然后打印出s1、s2的內(nèi)容。接下來把s2的內(nèi)容變?yōu)椤癶aha!”,接著再查看s1和s2的內(nèi)容。運(yùn)行結(jié)果如下:
由運(yùn)行結(jié)果可見,改變s2的內(nèi)容后,s1的內(nèi)容也變了。這不是想要得到的結(jié)果。這是默認(rèn)的拷貝構(gòu)造函數(shù)造成的。默認(rèn)的拷貝構(gòu)造函數(shù)所做的工作是“讓位拷貝”,拷貝完后,s1和s2的成員變量ch是一樣的,都指向創(chuàng)建s1時(shí)所分配的那片內(nèi)存空間。這時(shí)只要通過其中一個(gè)指針修改其指向的內(nèi)容,它們兩個(gè)指向的內(nèi)存內(nèi)容就會(huì)一樣。所以打印出了Afterchange---s1:haha!和Aftorchange---s2:haha!。同時(shí)也說明沒有給s2分配內(nèi)存,s2的ch指向的是s1的內(nèi)存。當(dāng)給String定義析構(gòu)函數(shù)來釋放分配的內(nèi)存(下一節(jié)將講述)時(shí),如果一個(gè)對(duì)象的析構(gòu)函數(shù)被調(diào)用后(如s1),也就是將它們都指向的那片空間給釋放了,那么s2再需要得到它的內(nèi)容時(shí)將會(huì)出錯(cuò),因?yàn)槟瞧臻g也被釋放了。或者s2的析構(gòu)函數(shù)再次調(diào)用時(shí),也將會(huì)出錯(cuò)。這就是“兩次釋放”的問題。釋放已被釋放的空間會(huì)出錯(cuò),那么怎么解決呢?問題出在“Strings1(strlen(s),s);”這句,因?yàn)樗鼤?huì)自動(dòng)調(diào)用系統(tǒng)生成的默認(rèn)拷貝構(gòu)造函數(shù),導(dǎo)致兩個(gè)對(duì)象的指針成員指向了同一片空間。那么就應(yīng)該自定義拷貝構(gòu)造函數(shù),給s2也分配內(nèi)存空間,然后將s1成員指針指向的內(nèi)容拷貝到新分配的內(nèi)存空間中。下面為自定義的拷貝構(gòu)造函數(shù):
String::String(constString&s){ curLen=s.getLenth();//得到字符串長(zhǎng)度
ch=(char*)malloc(curLen+1); //分配內(nèi)存空間,最后一個(gè)是給“\0”的 strcpy(ch,s.getString()); //拷貝內(nèi)容
cout<<"拷貝構(gòu)造!"<<endl;}
新加的成員函數(shù)getLenth()的實(shí)現(xiàn)如下:
String::getLenth()const{ returncurLen;}
再次運(yùn)行得到的結(jié)果如下:
這下結(jié)果就正確了,改了s2后,s1的內(nèi)容并沒有變。這樣,s1、s2都有屬于自己的空間了。這次定義的拷貝構(gòu)造函數(shù)做的是“深拷貝”。因此,當(dāng)定義類時(shí),如果成員變量都是內(nèi)置數(shù)據(jù)類型(不包括指針),那么系統(tǒng)生成的默認(rèn)構(gòu)造函數(shù)是能勝任的。但是當(dāng)類中有指向另一塊資源的成員變量(如指針、數(shù)組)時(shí),則必須定義自己的拷貝構(gòu)造函數(shù)來進(jìn)行“深拷貝”。
最后再講解一下拷貝構(gòu)造與賦值操作符(將在以后的章節(jié)中講述)的區(qū)別。由于拷貝構(gòu)造是構(gòu)造函數(shù),主要用于創(chuàng)建對(duì)象,因此當(dāng)一個(gè)對(duì)象從無到有時(shí)調(diào)用的是拷貝構(gòu)造。但是當(dāng)一個(gè)對(duì)象已存在,需要對(duì)它進(jìn)行重新賦值時(shí),調(diào)用的就是賦值操作符。例如:
Strings1=s2; //調(diào)用拷貝構(gòu)造函數(shù)
Strings3; //調(diào)用默認(rèn)構(gòu)造函數(shù)
s3=s1; //調(diào)用賦值操作符,重新賦值構(gòu)造函數(shù)是用于做初始化工作的,但有的成員變量在構(gòu)造函數(shù)體里面不能初始化,這就需要初始化參數(shù)列表。默認(rèn)構(gòu)造函數(shù)和默認(rèn)拷貝構(gòu)造函數(shù)一般被我們忽略。因?yàn)榧幢悴欢x它們,系統(tǒng)也會(huì)生成一個(gè)。但是有些時(shí)候系統(tǒng)生成的不能完全勝任,那就得親自定義它們了。3.2析構(gòu)函數(shù)一個(gè)類有了構(gòu)造函數(shù),這樣編譯器在適當(dāng)?shù)臅r(shí)候會(huì)自動(dòng)調(diào)用它來確保初始化的正確完成,那么當(dāng)對(duì)象被銷毀時(shí)也得有一個(gè)函數(shù)自動(dòng)來做一些清除工作。這就是析構(gòu)函數(shù)。下面分別講述析構(gòu)函數(shù)的作用和析構(gòu)函數(shù)的調(diào)用。3.2.1析構(gòu)函數(shù)的作用構(gòu)造函數(shù)的一個(gè)目的是自動(dòng)獲得資源,如構(gòu)造函數(shù)可能給一個(gè)緩沖區(qū)分配內(nèi)存或打開一個(gè)文件。構(gòu)造函數(shù)獲得了資源,必須有相應(yīng)的操作來釋放這些資源。析構(gòu)函數(shù)就是一個(gè)用來釋放資源的特殊成員函數(shù),它是構(gòu)造函數(shù)的補(bǔ)充服務(wù)。
首先來看看析構(gòu)函數(shù)的格式。由于析構(gòu)函數(shù)與構(gòu)造函數(shù)的功能相反,因此加前綴“~”以示區(qū)別。如下為String類定義一個(gè)析構(gòu)函數(shù):
String::~String(){ cout<<"析構(gòu)函數(shù)!"<<endl;}
它也是一個(gè)特殊的成員函數(shù),函數(shù)名與類名相同,沒有參數(shù),沒有返回值。函數(shù)前加一個(gè)前綴“~”。析構(gòu)函數(shù)沒有參數(shù),所以不能重載。盡管能定義多個(gè)構(gòu)造函數(shù),但是只能定義一個(gè)析構(gòu)函數(shù)。再回頭看看String的一個(gè)構(gòu)造函數(shù)的實(shí)現(xiàn):String::String(intlen,char*src){ ch=(char*)malloc(len+1); //分配內(nèi)存,多分配一個(gè)來存放“\0” curLen=len; memset(ch,0,len+1);//初始化內(nèi)存為零
strcpy(ch,src);//初始化ch,將src指的字符串拷貝到ch指向的控件中
cout<<"帶參構(gòu)造!"<<endl;}
這樣在構(gòu)造函數(shù)中調(diào)用malloc在堆上分配了內(nèi)存。當(dāng)對(duì)象銷毀時(shí)怎樣來釋放這些內(nèi)存呢?如果不釋放就會(huì)造成內(nèi)存泄露??梢哉{(diào)用free來釋放,例如:
String::~String(){free(ch); //釋放內(nèi)存
cout<<"析構(gòu)函數(shù)!"<<endl;}
當(dāng)然,如果在構(gòu)造函數(shù)或在其他地方申請(qǐng)了資源,或者打開了文件等,在析構(gòu)函數(shù)中可以釋放它們,或者關(guān)閉文件等。
析構(gòu)函數(shù)不只限于釋放資源,它還可以執(zhí)行任何類的設(shè)計(jì)者希望在對(duì)象被銷毀之前的操作。因?yàn)楫?dāng)析構(gòu)函數(shù)執(zhí)行完畢后,通過構(gòu)造函數(shù)創(chuàng)建的對(duì)象將不存在,所以在析構(gòu)函數(shù)里可以執(zhí)行最后一次使用對(duì)象后要執(zhí)行的操作。不像構(gòu)造函數(shù)和拷貝構(gòu)造函數(shù),編譯器總是為用戶生成默認(rèn)的析構(gòu)函數(shù)。默認(rèn)析構(gòu)函數(shù)銷毀非靜態(tài)成員變量。對(duì)于每個(gè)類類型的成員變量,默認(rèn)析構(gòu)函數(shù)會(huì)調(diào)用成員的析構(gòu)函數(shù)來銷毀它們。但是默認(rèn)的析構(gòu)函數(shù)對(duì)內(nèi)置數(shù)據(jù)類型或者復(fù)合類型不會(huì)產(chǎn)生作用,如指針不會(huì)自動(dòng)調(diào)用free函數(shù)來釋放指針指向的空間。因此,需自己定義析構(gòu)函數(shù),在析構(gòu)函數(shù)中調(diào)用free函數(shù)來釋放。
默認(rèn)析構(gòu)函數(shù)與默認(rèn)構(gòu)造函數(shù)及默認(rèn)拷貝構(gòu)造函數(shù)不一樣,即使定義了自己的構(gòu)造函數(shù),編譯器生成的默認(rèn)析構(gòu)函數(shù)也會(huì)被調(diào)用。例如下面的Person類:
#include<iostream>usingnamespacestd;classPerson{ intage; Stringname; //... Person(){ cout<<"Person默認(rèn)構(gòu)造!"<<endl; } ~Person(){//析構(gòu)函數(shù)
//什么也不做
}};String的默認(rèn)構(gòu)造改為:
String::String(){ curLen=0; maxLen=256; ch=NULL; cout<<"String默認(rèn)構(gòu)造!"<<endl;}
析構(gòu)函數(shù)改為:
String::~String(){ if(ch) //如果ch不為NULL,則釋放內(nèi)存
free(ch); cout<<"String析構(gòu)函數(shù)!"<<endl;}String的頭文件和實(shí)現(xiàn)文件如下:
//String.h#ifndefSTRING_H#defineSTRING_HclassString{ public: String(); ~String();private: intcurLen; intmaxLen;char*ch;};#endef//String.cpp#include"String.h"String::String(){ curLen=0; maxLen=256; ch=NULL; cout<<"String默認(rèn)構(gòu)造!"<<endl;}String::~String(){ if(ch) //如果ch不為NULL,則釋放內(nèi)存 free(ch); cout<<"String析構(gòu)函數(shù)!"<<endl;}
主程序?yàn)椋?/p>
#include"String.h"usingnamespacestd;intmain(){ Personp;return0;}
運(yùn)行結(jié)果如下:
程序首先調(diào)用String的默認(rèn)構(gòu)造來初始化成員變量name,接著調(diào)用自己的默認(rèn)構(gòu)造。然后調(diào)用自定義的Person的析構(gòu)函數(shù),但是函數(shù)體里面什么也沒做,最后調(diào)用了String的析構(gòu)函數(shù)。這說明調(diào)用完自己定義的析構(gòu)函數(shù)后,還會(huì)調(diào)用編譯器生成的默認(rèn)析構(gòu)函數(shù),而默認(rèn)析構(gòu)函數(shù)會(huì)調(diào)用成員變量的析構(gòu)函數(shù),所以打印了“String析構(gòu)函數(shù)!”這一句。但是對(duì)內(nèi)置數(shù)類型沒有產(chǎn)生什么效果,如成員變量age。3.2.2析構(gòu)函數(shù)的調(diào)用當(dāng)創(chuàng)建對(duì)象時(shí),構(gòu)造函數(shù)會(huì)被自動(dòng)調(diào)用。當(dāng)一個(gè)對(duì)象被銷毀時(shí),析構(gòu)函數(shù)會(huì)被自動(dòng)調(diào)用。例如當(dāng)對(duì)象超出它的作用域時(shí),也就是撤消類對(duì)象的時(shí)候:
#include"String.h"usingnamespacestd;Stringglobal; //全局對(duì)象
intmain(){ cout<<"inthemain!"<<endl; { StringautoString; //局部對(duì)象
cout<<"局部對(duì)象將超出作用域!"<<endl }//autoString的作用域
cout<<"willleavethemain"<<endl;return0;}String的析構(gòu)函數(shù)為:
String::~String(){ if(ch) free(ch); printf("String析構(gòu)函數(shù)!\n"); //注意在VC6.0下,此處最好調(diào)用此函數(shù)進(jìn)行輸出
}
運(yùn)行結(jié)果如下:
進(jìn)入main函數(shù)前之所以有個(gè)默認(rèn)構(gòu)造會(huì)被調(diào)用,是因?yàn)橛袀€(gè)全局對(duì)象global。全局對(duì)象是在進(jìn)入main函數(shù)前被創(chuàng)建的,接著局部對(duì)象autoString被創(chuàng)建。但是autoString的作用域就是在兩個(gè)大括號(hào)之間,所以在程序運(yùn)行到“}”后,超出了autoString的作用域,析構(gòu)函數(shù)被自動(dòng)調(diào)用。最后在main函數(shù)執(zhí)行完后,也已經(jīng)超出了全局對(duì)象global的作用域,String的析構(gòu)函數(shù)再次被調(diào)用。注意:在VC6.0下如果析構(gòu)函數(shù)中的輸出信息用cout輸出,則最后一句的打印將不會(huì)被看見。因?yàn)閏out也被定義為iostream的一個(gè)全局對(duì)象,所以cout可能在全局對(duì)象global析構(gòu)之前就已經(jīng)被析構(gòu)了。
如果一個(gè)對(duì)象是在堆上被動(dòng)態(tài)創(chuàng)建的(下一節(jié)將講述),則當(dāng)指向?qū)ο蟮闹羔槺粍h除掉時(shí),析構(gòu)函數(shù)也會(huì)被自動(dòng)調(diào)用。如下面的代碼:
String*s=newString();deletes;
當(dāng)執(zhí)行“deletes;”時(shí),析構(gòu)函數(shù)會(huì)被自動(dòng)調(diào)用。當(dāng)指向?qū)ο蟮闹羔樆蛞贸鲎饔糜驎r(shí),對(duì)象的析構(gòu)函數(shù)不會(huì)被自動(dòng)調(diào)用。如果不刪除掉指向?qū)ο蟮闹羔?是用new產(chǎn)生的),將會(huì)出現(xiàn)內(nèi)存泄露。所以必須刪除掉用new在堆上分配的對(duì)象指針。下面講解析構(gòu)函數(shù)的調(diào)用順序。首先回顧一下構(gòu)造函數(shù)的調(diào)用順序,構(gòu)造函數(shù)是從類層次的最根處開始(有關(guān)繼承的構(gòu)造與析構(gòu)順序?qū)⒃诘?0章中講述),在每一層中,首先調(diào)用基類的構(gòu)造函數(shù),然后調(diào)用成員對(duì)象的構(gòu)造函數(shù),且成員的構(gòu)造函數(shù)的順序與定義類時(shí)成員的順序一致。析構(gòu)函數(shù)則嚴(yán)格按照與構(gòu)造函數(shù)相反的順序執(zhí)行。如前面講述的Person類:
#ifndefPERSON_H#definePERSON_H#include<iostream>#include<string>usingnamespacestd;classPerson{public: Person(){ cout<<"Person默認(rèn)構(gòu)造!"<<endl; } ~Person(){ //析構(gòu)函數(shù)
cout<<"Person析構(gòu)構(gòu)造!"<<endl; }private: Stringname; Stringaddress; //...};#endif
構(gòu)造的時(shí)候先構(gòu)造name,然后構(gòu)造address;析構(gòu)的時(shí)候先析構(gòu)address,然后再析構(gòu)name。如果Person繼承了類Animal,則構(gòu)造的時(shí)候先構(gòu)造Animal,接著構(gòu)造name和address;析構(gòu)的時(shí)候先析構(gòu)address,接著再析構(gòu)name,最后析構(gòu)Animal。詳細(xì)講解將在第10章中介紹。3.3new和delete用于對(duì)象全局對(duì)象和局部對(duì)象的生命期是嚴(yán)格定義的,程序員不能以任何方式改變它們的生命期。但是,有時(shí)候需要一些對(duì)象,它們的生命期能被程序員控制。也就是說它們的分配和釋放可以根據(jù)程序運(yùn)行時(shí)的操作來決定。例如,在寫程序時(shí),如果有錯(cuò)誤,則應(yīng)把這個(gè)錯(cuò)誤信息打印出來。這時(shí)需新建一個(gè)String類的對(duì)象來存放錯(cuò)誤信息。如果程序不出錯(cuò),則這個(gè)String的對(duì)象將不會(huì)被創(chuàng)建。如果程序出錯(cuò),將會(huì)創(chuàng)建這個(gè)對(duì)象,而且這個(gè)錯(cuò)誤消息可能每次都不一樣,因?yàn)槊看芜\(yùn)行時(shí)產(chǎn)生的錯(cuò)誤類型可能不一樣,也就是說錯(cuò)誤信息的長(zhǎng)度隨程序運(yùn)行變化而變化。預(yù)先不知道分配多長(zhǎng)的字符串,允許程序員完全控制它的分配與釋放的對(duì)象叫做動(dòng)態(tài)創(chuàng)建的對(duì)象。下面將介紹如何動(dòng)態(tài)創(chuàng)建對(duì)象。3.3.1動(dòng)態(tài)創(chuàng)建對(duì)象要想動(dòng)態(tài)創(chuàng)建對(duì)象,首先必須得知道動(dòng)態(tài)創(chuàng)建的對(duì)象存放在哪里。在內(nèi)存空間中有一片空間叫“堆”(也叫自由空間),這片內(nèi)存空間程序員是可以拿來用的,程序員可以在這片空間上分配內(nèi)存,用完后將其釋放。動(dòng)態(tài)創(chuàng)建的對(duì)象就存放在這片堆上。在前面的例子中講到了兩個(gè)函數(shù)——?malloc和free,malloc是用來在堆上分配內(nèi)存的,free是用來釋放內(nèi)存的。這樣可以動(dòng)態(tài)地創(chuàng)建一個(gè)String對(duì)象,例如:
String*s=(String*)malloc(sizeof(String));這樣就在堆上分配了sizeof(String)字節(jié)數(shù)的空間。由于malloc返回的是void*,因此必須強(qiáng)制轉(zhuǎn)型。
分配的內(nèi)存用完后用free釋放:
free(s);經(jīng)過觀察可以發(fā)現(xiàn),上面創(chuàng)建的對(duì)象并沒有被初始化,而使用沒有初始化的對(duì)象是不安全的。但是,構(gòu)造函數(shù)又不能被顯式地調(diào)用。要初始化這個(gè)對(duì)象,必須寫一個(gè)initial()函數(shù),在malloc后調(diào)用此初始化函數(shù)。同樣,當(dāng)free調(diào)用對(duì)象指針時(shí),對(duì)象的析構(gòu)函數(shù)也并沒有被調(diào)用。那怎么辦呢?C++語言的設(shè)計(jì)者為了方便在堆上創(chuàng)建對(duì)象,把分配內(nèi)存和初始化結(jié)合在一起,把銷毀對(duì)象和釋放內(nèi)存結(jié)合在一起。這就是將要講到的new和delete。當(dāng)想動(dòng)態(tài)創(chuàng)建對(duì)象時(shí),可以直接這樣寫:
String*s=newString();或String*s=newString;
這樣不用關(guān)心對(duì)象的大小,不用強(qiáng)制轉(zhuǎn)換指針。更重要的是,不用自己寫初始化函數(shù)來初始化這個(gè)對(duì)象。因?yàn)閚ew表達(dá)式會(huì)自動(dòng)調(diào)用相應(yīng)的構(gòu)造函數(shù)。這一句在運(yùn)行時(shí)等價(jià)于“malloc(sizeof(String));”,并用默認(rèn)構(gòu)造Sring()來初始化此對(duì)象。如果不想用默認(rèn)構(gòu)造來初始化,可以調(diào)用帶參的構(gòu)造函數(shù)來初始化動(dòng)態(tài)創(chuàng)建的對(duì)象:
String*s=newString("Hello!");當(dāng)不需要此對(duì)象時(shí),可以用delete將其銷毀。
deletes;delete表達(dá)式首先調(diào)用析構(gòu)函數(shù),然后釋放內(nèi)存(可能調(diào)用free())。new表達(dá)式返回的是一個(gè)對(duì)象的指針,在用delete銷毀時(shí)需要此對(duì)象指針。s就是指向一個(gè)對(duì)象的指針。delete只用于銷毀用new創(chuàng)建的對(duì)象。
如果程序這樣寫:
char*c=newchar;
這樣,即可動(dòng)態(tài)創(chuàng)建一個(gè)內(nèi)置數(shù)據(jù)類型。也就是用new不但能動(dòng)態(tài)創(chuàng)建對(duì)象,而且很容易動(dòng)態(tài)創(chuàng)建內(nèi)置數(shù)據(jù)類型。當(dāng)不需要時(shí),用delete釋放內(nèi)存。
deletec;
當(dāng)然,由于內(nèi)置數(shù)據(jù)類型沒有構(gòu)造函數(shù)和析構(gòu)函數(shù),因此使用new的時(shí)候也不會(huì)調(diào)用構(gòu)造函數(shù),只是分配內(nèi)存而已,在銷毀時(shí)也只是釋放內(nèi)存而沒有調(diào)用析構(gòu)函數(shù)。需要注意的是,如果delete一個(gè)void*的指針,程序可能會(huì)出錯(cuò)。如將上面的程序?qū)憺椋?/p>
void*c=newchar;deletec;這樣程序是正確的,因?yàn)閯?chuàng)建的是內(nèi)置數(shù)據(jù),delete時(shí)只需釋放內(nèi)存即可。但是如果寫為:
void*s=newString;deletes;因?yàn)関oid*可以存放任何指針,所以上面的代碼編譯時(shí)不會(huì)有錯(cuò)。上面的程序執(zhí)行的結(jié)果為:
String默認(rèn)構(gòu)造!可見,當(dāng)執(zhí)行new表達(dá)式時(shí),編譯器調(diào)用了構(gòu)造函數(shù),但是執(zhí)行delete時(shí)則沒有調(diào)用析構(gòu)函數(shù)。這是因?yàn)閟是個(gè)void*?的指針,也就是說deletevoid*的指針并不調(diào)用析構(gòu)函數(shù),這樣造成了內(nèi)存泄露。當(dāng)然s指向的內(nèi)存已經(jīng)被釋放了。內(nèi)存泄露是由于沒有調(diào)用String的析構(gòu)函數(shù)造成的。因?yàn)榫幾g器不知道s指向的是什么類型,所以它沒法調(diào)用析構(gòu)函數(shù)。如果把上面的代碼改為:
void*s=newString;delete(String*)s;
則運(yùn)行結(jié)果如下:
這次編譯器知道了s指向的類型,所以調(diào)用了析構(gòu)函數(shù)。因此,在寫程序時(shí)最好不要把new返回的指針賦給一個(gè)void*。當(dāng)然,有時(shí)這樣做也是有用的。
delete應(yīng)和new配對(duì)使用,malloc應(yīng)和free配對(duì)使用。如交錯(cuò)使用程序可能會(huì)出錯(cuò)。這是因?yàn)椋绻鹒ree調(diào)用new創(chuàng)建的對(duì)象,程序可能會(huì)因?yàn)闆]有調(diào)用析構(gòu)函數(shù)而出錯(cuò);刪除malloc創(chuàng)建的對(duì)象理論上不會(huì)出錯(cuò),但是這樣可讀性會(huì)很差。3.3.2new和delete用于數(shù)組當(dāng)一次需要?jiǎng)討B(tài)創(chuàng)建多個(gè)對(duì)象時(shí),仍然可以使用new和delete??梢栽诙焉蟿?chuàng)建一個(gè)動(dòng)態(tài)對(duì)象數(shù)組,例如:
String*s=newString[10];
運(yùn)行結(jié)果如下:
此句代碼在堆上為10個(gè)對(duì)象分配充足的內(nèi)存空間,然后為每個(gè)對(duì)象調(diào)用構(gòu)造函數(shù)。但要注意的是,這里編譯器只能調(diào)用默認(rèn)構(gòu)造函數(shù),也就是為每個(gè)對(duì)象調(diào)用構(gòu)造函數(shù)String()。如果String沒有默認(rèn)構(gòu)造函數(shù)(因?yàn)槎x了其他的構(gòu)造函數(shù),且沒有自己定義默認(rèn)構(gòu)造),那么上面的語句在編譯時(shí)將會(huì)出錯(cuò)。所以當(dāng)想創(chuàng)建String的數(shù)組時(shí),必須保證String類有默認(rèn)構(gòu)造函數(shù)。
C++并不能區(qū)分單個(gè)的對(duì)象指針和對(duì)象數(shù)組指針。如這樣寫:
String*s1=newString[10];String*s2=newString;
看上去好像s1和s2都是一樣的。但是當(dāng)這樣寫時(shí):
deletes1; //錯(cuò)誤
deletes2; //正確編譯器也不會(huì)報(bào)錯(cuò)??瓷先ズ孟褚粯?,都是為所給地址指向的對(duì)象調(diào)用析構(gòu)函數(shù),然后釋放內(nèi)存,但是在運(yùn)行時(shí)會(huì)出錯(cuò)。因?yàn)閟1指向的是10個(gè)對(duì)象,而s2只指向一個(gè)對(duì)象,對(duì)于s2是正確的。但對(duì)于s1,只對(duì)第一個(gè)對(duì)象調(diào)用了析構(gòu)函數(shù),還有9個(gè)對(duì)象的析構(gòu)函數(shù)沒有被調(diào)用,這樣會(huì)造成內(nèi)存的泄露。編譯器不知道此指針指的是一個(gè)對(duì)象還是一個(gè)對(duì)象數(shù)組,需聲明要?jiǎng)h除的是一個(gè)對(duì)象還是一個(gè)對(duì)象數(shù)組。將上面的代碼改為:
delete[]s1;空的括號(hào)告訴編譯器產(chǎn)生代碼,該代碼的任務(wù)是將數(shù)組創(chuàng)建時(shí)存放的對(duì)象數(shù)組取回,并為數(shù)組的每個(gè)對(duì)象調(diào)用析構(gòu)函數(shù)。上面代碼的運(yùn)行結(jié)果為:
這樣不會(huì)再造成內(nèi)存泄露了。記住一點(diǎn),當(dāng)用new動(dòng)態(tài)創(chuàng)建數(shù)組后,delete時(shí)不要忘了delete后面的[]。3.3.3內(nèi)存管理程序要運(yùn)行首先要申請(qǐng)到內(nèi)存,內(nèi)存對(duì)程序來說至關(guān)重要。特別是對(duì)于那些內(nèi)存很小的設(shè)備,如果程序存在內(nèi)存泄露,那么程序不久將會(huì)卡死,因?yàn)閮H有的內(nèi)存都被程序泄露完了。所以內(nèi)存管理很重要。
1.內(nèi)存分配方式內(nèi)存的分配方式有以下三種:
(1)靜態(tài)存儲(chǔ)區(qū)域:內(nèi)存在編譯時(shí)就分配好了,這塊內(nèi)存在程序整個(gè)運(yùn)行期間都存在。例如,全局變量和靜態(tài)變量都分配在靜態(tài)存儲(chǔ)區(qū)。如3.2.2節(jié)例子中的global就是分配在靜態(tài)存儲(chǔ)區(qū)域。(2)棧:在執(zhí)行函數(shù)時(shí),函數(shù)的參數(shù)和函數(shù)體內(nèi)的變量都是在棧上分配的。每次進(jìn)入函數(shù)體時(shí),都有自己的一份拷貝。在函數(shù)執(zhí)行完畢后這些變量會(huì)自動(dòng)被釋放。這些內(nèi)存都是自動(dòng)被創(chuàng)建,自動(dòng)被銷毀。
(3)堆(自由空間):也叫動(dòng)態(tài)內(nèi)存分配。程序運(yùn)行時(shí)用malloc或new申請(qǐng)的內(nèi)存就是在堆上的。用完后程序員負(fù)責(zé)用free或delete釋放內(nèi)存。動(dòng)態(tài)內(nèi)存的生命期由用戶決定,使用非常靈活,但問題也很多。2.常見內(nèi)存錯(cuò)誤及其解決方法1)內(nèi)存未分配成功,卻使用了它當(dāng)使用malloc或new分配內(nèi)存時(shí),可能分配失敗,也就是說返回的指針可能為NULL。而如果使用一個(gè)NULL的指針,則在運(yùn)行時(shí)肯定會(huì)出錯(cuò)。要解決這個(gè)問題,在使用這個(gè)指針時(shí)應(yīng)先判斷它是否為空。例如:
String*s1=newString[1000];
當(dāng)要分配1000個(gè)String時(shí)有可能內(nèi)存不夠,返回了一個(gè)NULL。這樣后面的程序用這個(gè)s1時(shí)程序肯定會(huì)出錯(cuò)。應(yīng)該先對(duì)這個(gè)指針進(jìn)行判斷:if(s1==NULL){ cout<<"內(nèi)存分配失??!"<<endl;}else{ … //使用指針s1}2)內(nèi)存越界在for循環(huán)變量數(shù)組時(shí)很容易產(chǎn)生越界。例如:
inta[10];for(inti=1;i<=10;i++){ a[i]=i;}
這段代碼編譯時(shí)并沒有錯(cuò)誤,但是會(huì)出現(xiàn)運(yùn)行錯(cuò)誤,有時(shí)候這種錯(cuò)誤很難找。應(yīng)該注意到,當(dāng)對(duì)a[10]賦值時(shí)a[10]已經(jīng)不在a數(shù)組的范圍了。a[10]是個(gè)未知區(qū)域。數(shù)組a最多只能引用到下標(biāo)9,因?yàn)橄聵?biāo)是從0開始的。所以上面的程序可以改為:inta[10];for(inti=0;i<=9;i++){ a[i]=i;}
索引從0到9才是正確的,這樣就不會(huì)越界了。再看一段代碼:
chara[6];strcpy(a,"Hello!");printf("%s",a);
看上去沒什么錯(cuò)誤,運(yùn)行時(shí)也沒有錯(cuò)誤。但是這段代碼隱藏著一個(gè)錯(cuò)誤,即內(nèi)存越界。數(shù)組a是6個(gè)字節(jié),Hello!也是6個(gè)字節(jié)。但是最后還有個(gè)結(jié)束符“\0”,這樣總共加起來是7個(gè)字節(jié)。當(dāng)把?"Hello!"?拷貝到數(shù)組a時(shí),最后的“\0”就造成了內(nèi)存越界,因?yàn)樗豢截惖絼e的地方,數(shù)組a并沒有分配夠內(nèi)存。當(dāng)然這個(gè)程序運(yùn)行時(shí)沒有出現(xiàn)問題,但是如果數(shù)組a后面的內(nèi)存中是很重要的數(shù)據(jù),則在a后面的內(nèi)存中的數(shù)據(jù)就被修改了。這樣可能會(huì)造成程序的運(yùn)行錯(cuò)誤,甚至可能造成程序的崩潰。如果對(duì)程序進(jìn)行修改,分配夠內(nèi)存:
chara[6+1];strcpy(a,"Hello!");printf("%s",a);
這樣就不會(huì)再出現(xiàn)內(nèi)存越界了。注意,字符串后面有個(gè)“\0”作為結(jié)束符,分配內(nèi)存時(shí)不要忘記為它分配內(nèi)
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 二零二五年度牛糞有機(jī)肥原料采購合同范本4篇
- 二零二五年度家具原材料采購合同4篇
- 2025年度智能儲(chǔ)藏室與車位租賃買賣合同模板4篇
- 二零二五年度外匯貸款合同違約責(zé)任范本
- 2025年度房地產(chǎn)估價(jià)咨詢合同示范文本
- 2025年度民辦學(xué)校教師學(xué)術(shù)交流與合作合同4篇
- 二零二五年度外教兼職學(xué)術(shù)研究資助合同
- 二零二五年度國際酒店前臺(tái)服務(wù)員聘用合同范本4篇
- 2025年度廣告代理合同終止協(xié)議范文
- 二零二五年度汽車交易傭金合同電子版
- 2025屆河北省衡水市衡水中學(xué)高考仿真模擬英語試卷含解析
- 新修訂《保密法》知識(shí)考試題及答案
- 電工基礎(chǔ)知識(shí)培訓(xùn)課程
- 住宅樓安全性檢測(cè)鑒定方案
- 廣東省潮州市潮安區(qū)2023-2024學(xué)年五年級(jí)上學(xué)期期末考試數(shù)學(xué)試題
- 市政道路及設(shè)施零星養(yǎng)護(hù)服務(wù)技術(shù)方案(技術(shù)標(biāo))
- 選擇性必修一 期末綜合測(cè)試(二)(解析版)2021-2022學(xué)年人教版(2019)高二數(shù)學(xué)選修一
- 《論語》學(xué)而篇-第一課件
- 《寫美食有方法》課件
- 學(xué)校制度改進(jìn)
- 各行業(yè)智能客服占比分析報(bào)告
評(píng)論
0/150
提交評(píng)論