構(gòu)造函數(shù)產(chǎn)生的點(diǎn)及原因
我相信很多人對構(gòu)造函數(shù)在什么時(shí)候產(chǎn)生,以及產(chǎn)生的原因,理解得不是很透徹;更有甚者認(rèn)為默認(rèn)構(gòu)造函數(shù)和復(fù)制構(gòu)造函數(shù)是一定會(huì)產(chǎn)生的,成員變量就應(yīng)該在初始化參數(shù)列表中進(jìn)行初始化,當(dāng)然這些是初學(xué)者的認(rèn)識(shí),下面分享一下我的看法。
構(gòu)造函數(shù)不負(fù)責(zé)分配內(nèi)存,只是在分配好的一塊內(nèi)存中進(jìn)行賦值操作.這一點(diǎn)我們可以很容易從new/delete與malloc/free的區(qū)別中看出來,malloc/free只負(fù)責(zé)分配內(nèi)存不負(fù)責(zé)初始化,而new/delete不僅負(fù)責(zé)分配內(nèi)存,如果對象存在相應(yīng)的夠著函數(shù),就會(huì)調(diào)用相應(yīng)的構(gòu)造函數(shù),如果不存在當(dāng)然就不調(diào)用,如int *i=new int[10];int類型沒有構(gòu)造函數(shù),所以new只負(fù)責(zé)分配40字節(jié)的內(nèi)存,將首地址賦給i,沒有其他多于的操作,而string *s=new string();不僅要分配string一個(gè)實(shí)例所需要的內(nèi)存,還要調(diào)用string的default constructor,說這么多我只想說,構(gòu)造函數(shù)不負(fù)責(zé)分配內(nèi)存,只負(fù)責(zé)初始化,也就是說一個(gè)實(shí)例你想不想初始化,怎么初始化那是你程序員的事,跟編譯器無關(guān),編譯器只在需要構(gòu)造函數(shù)的時(shí)候,才會(huì)合成相應(yīng)的構(gòu)造函數(shù),不需要的時(shí)候就不會(huì)合成。
先看一個(gè)簡單的類:
class Point { public: int x; int y; };
執(zhí)行以下簡單的代碼:
int main() { { Point *p=new Point; cout<<p->x<<endl;//第4行 Point p1; //cout<<p1.x;//第6行 p1.x=0; p1.y=0; Point p2(p1);//第9行 } getchar(); return 0; }
第4行沒有任何問題,x是個(gè)隨機(jī)數(shù),第6行就報(bào)錯(cuò)了,說x沒有初始化,說明編譯器沒有自動(dòng)合成default constructor構(gòu)造函數(shù),隨機(jī)數(shù)就說明沒有初始化啊,難道編譯器會(huì)傻不啦嘰的給你初始化成一個(gè)隨機(jī)數(shù),你看看隨機(jī)函數(shù)的源碼,你知道人家有多努力的幫你隨機(jī)了,人家背地里默默地幫你做了很多事的,只能說明編譯器沒有幫你初始化,初始化工作就是程序員的事,至少C++是這樣,C#編譯器會(huì)幫你初始化。
第9行也不會(huì)導(dǎo)致產(chǎn)生copy constructor,因?yàn)檫@個(gè)類簡單到實(shí)在沒有必要合成復(fù)制構(gòu)造函數(shù),編譯器完全可以在給p2分配完地址后,直接:Memcpy(&p2,&p1,sizeof(Point));不就是把一個(gè)地址的內(nèi)容直接拷貝到另一個(gè)地址中去嘛,不用通夠著函數(shù)的,構(gòu)造函數(shù)還得一個(gè)的給字段賦值,多慢,在對象很簡單的境況下,用memset進(jìn)行初始化,memcpy進(jìn)行賦值比給字段一個(gè)一個(gè)的賦值是要快一些的。說了這么多廢話,那夠著函數(shù)在什么情況下回產(chǎn)生呢?
一句話:在逐位拷貝解決不了問題的情況下,就得合成構(gòu)造函數(shù)。說到這里其實(shí)大家差不多可以散了(只要你能深層次的理解這句話),但是我還有很多話要對你說。
需要產(chǎn)生default constructor的情況:
1:成員變量含有default constructor
2:父類中含有default constructor
3:含有virtual function
4:繼承關(guān)系中存在virtual繼承
其實(shí)這4點(diǎn)概括一下就是兩點(diǎn):需要執(zhí)行default constructor和實(shí)例中存在指向方法表的指針。在這4種情況下,僅僅是分配所需要的內(nèi)存是不夠的,前兩種需要執(zhí)行相應(yīng)的default constructor,相應(yīng)的代碼當(dāng)然是放在當(dāng)前對象的默認(rèn)構(gòu)造函數(shù)中,后兩種情況是因?yàn)橹赶蚍椒ū淼闹羔樞枰跏蓟?,哥,這個(gè)必須初始化啊,是編譯器的職責(zé)啊,而字段是否初始化時(shí)程序員的職責(zé)。除了這4種情況,默認(rèn)構(gòu)造函數(shù)是不需要合成的,我都說了,構(gòu)造函數(shù)不負(fù)責(zé)分配內(nèi)存,編譯器也不負(fù)責(zé)初始化,在沒事的情況下編譯器是不會(huì)多事的,如果程序員多事的加上了相關(guān)的構(gòu)造函數(shù),那絕對是手賤,你的代碼很難快過編譯器。
復(fù)制構(gòu)造函數(shù)也是在需要的時(shí)候才合成,不需要的時(shí)候就不會(huì)合成,還是那句話,在逐位拷貝解決不了問題的情況下,就會(huì)合成復(fù)制構(gòu)造函數(shù),在里面做一些力所能及的事。
需要產(chǎn)生復(fù)制構(gòu)造函數(shù)的情況:
1:成員變量含有copy constructor
2:父類含有copy constructor
3:含有virtual function
4:繼承關(guān)系中含有虛繼承
貌似產(chǎn)生的原因和default constructor差不多啊,還是要調(diào)用相應(yīng)的構(gòu)造函數(shù),給指向方法表的指針賦值。如將一個(gè)子類對象賦給父類對象,就需要修改方法表的指向,因?yàn)橹惡透割惖姆椒ū砜赡苁遣灰粯拥陌?,更多原因請看我?a href="http://www.rzrgm.cn/hlxs/p/3214062.html">虛方法的調(diào)用是怎么實(shí)現(xiàn)的(單繼承VS多繼承)
構(gòu)造函數(shù)是不會(huì)有事沒事的產(chǎn)生的,不要再說那6個(gè)函數(shù)是一定會(huì)產(chǎn)生的了,只是在需要的時(shí)候才產(chǎn)生嗎,還有一點(diǎn)讓我受不了的就是關(guān)于初始化參數(shù)列表,瘋狂的迷信初始化參數(shù)列表,導(dǎo)致初始化參數(shù)列表很長,當(dāng)然用初始化參數(shù)列表是不會(huì)響應(yīng)性能啊,而且有的成員變量只能在初始化參數(shù)列表中初始化,但是有的成員變量在初始化參數(shù)列表中初始化和在構(gòu)造函數(shù)中初始化性能是一樣的。那干嘛要把初始化參數(shù)列表搞得那么長呢?看著就蛋疼。
如果一個(gè)成員變量沒有默認(rèn)構(gòu)造構(gòu)造函數(shù),且也不需要合成默認(rèn)構(gòu)造函數(shù),且可以不再初始化參數(shù)列表中初始化,那么他在初始化參數(shù)列表中初始化和在構(gòu)造函數(shù)內(nèi)初始化是一樣的,原因很簡單,構(gòu)造函數(shù)不會(huì)針對這樣的成員變量合成多于的代碼,在哪里初始化都一樣,但是成員變量如果有相關(guān)構(gòu)造函數(shù),編譯器就會(huì)幫你調(diào)用,初始化參數(shù)列表中的代碼會(huì)放到構(gòu)造函數(shù)中,如果變量在初始化參數(shù)列表中初始化,就只初始化一次,而如果在夠著函數(shù)中初始化,編譯器看你沒有在初始化參數(shù)列表中初始化,以為你沒初始化,就產(chǎn)生代碼幫你初始化了,而你又在構(gòu)造函數(shù)中初始化那就初始化兩次了,這就是為什么很多初學(xué)者認(rèn)為初始化參數(shù)列表性能高的原因。
綜上所述:在逐位拷貝解決不了問題時(shí),編譯器才合成相關(guān)的構(gòu)造函數(shù),執(zhí)行相關(guān)的代碼,在初始化的時(shí)候,看你沒有初始化,而又有相關(guān)的構(gòu)造函數(shù),于是就幫你調(diào)用了,其他情況編譯器概不負(fù)責(zé),那是我們程序員自己的事。
浙公網(wǎng)安備 33010602011771號(hào)