C++類(lèi)對象的復制-拷貝構造函數(深拷貝,淺拷貝),進(jìn)一步理解類(lèi)成員的操作!
在學(xué)習這一章內容前我們已經(jīng)學(xué)習過(guò)了類(lèi)的構造函數和析構函數的相關(guān)知識,對于普通類(lèi)型的對象來(lái)說(shuō),他們之間的復制是很簡(jiǎn)單的,例如:
int a = 10;
int b =a;
自己定義的類(lèi)的對象同樣是對象,誰(shuí)也不能阻止我們用以下的方式進(jìn)行復制,例如:
//程序作者:管寧
//站點(diǎn):www.cndev-lab.com
//所有稿件均有版權,如要轉載,請務(wù)必著(zhù)名出處和作者
#include <iostream>
usingnamespacestd;
classTest
{
public:
Test(inttemp)
{
p1=temp;
}
protected:
intp1;
};
voidmain()
{
Test a(99);
Test b=a;
}
普通對象和類(lèi)對象同為對象,他們之間的特性有相似之處也有不同之處,類(lèi)對象內部存在成員變量,而普通對象是沒(méi)有的,當同樣的復制方法發(fā)生在不同的對象上的時(shí)候,那么系統對他們進(jìn)行的操作也是不一樣的,就類(lèi)對象而言,相同類(lèi)型的類(lèi)對象是通過(guò)拷貝構造函數來(lái)完成整個(gè)復制過(guò)程的,在上面的代碼中,我們并沒(méi)有看到拷貝構造函數,同樣完成了復制工作,這又是為什么呢?因為當一個(gè)類(lèi)沒(méi)有自定義的拷貝構造函數的時(shí)候系統會(huì )自動(dòng)提供一個(gè)默認的拷貝構造函數,來(lái)完成復制工作。
下面,我們?yōu)榱苏f(shuō)明情況,就普通情況而言(以上面的代碼為例),我們來(lái)自己定義一個(gè)與系統默認拷貝構造函數一樣的拷貝構造函數,看看它的內部是如何工作的!
代碼如下:
//程序作者:管寧
//站點(diǎn):www.cndev-lab.com
//所有稿件均有版權,如要轉載,請務(wù)必著(zhù)名出處和作者
#include <iostream>
usingnamespacestd;
classTest
{
public:
Test(inttemp)
{
p1=temp;
}
Test(Test &c_t)//這里就是自定義的拷貝構造函數
{
cout<<"進(jìn)入copy構造函數"<
p1=c_t.p1;//這句如果去掉就不能完成復制工作了,此句復制過(guò)程的核心語(yǔ)句
}
public:
intp1;
};
voidmain()
{
Test a(99);
Test b=a;
cout<
cin.get();
}
上面代碼中的Test(Test &c_t)就是我們自定義的拷貝構造函數,拷貝構造函數的名稱(chēng)必須與類(lèi)名稱(chēng)一致,函數的形式參數是本類(lèi)型的一個(gè)引用變量,且必須是引用。
當用一個(gè)已經(jīng)初始化過(guò)了的自定義類(lèi)類(lèi)型對象去初始化另一個(gè)新構造的對象的時(shí)候,拷貝構造函數就會(huì )被自動(dòng)調用,如果你沒(méi)有自定義拷貝構造函數的時(shí)候系統將會(huì )提供給一個(gè)默認的拷貝構造函數來(lái)完成這個(gè)過(guò)程,上面代碼的復制核心語(yǔ)句就是通過(guò)Test(Test &c_t)拷貝構造函數內的p1=c_t.p1;語(yǔ)句完成的。如果取掉這句代碼,那么b對象的p1屬性將得到一個(gè)未知的隨機值;
下面我們來(lái)討論一下關(guān)于淺拷貝和深拷貝的問(wèn)題。
就上面的代碼情況而言,很多人會(huì )問(wèn)到,既然系統會(huì )自動(dòng)提供一個(gè)默認的拷貝構造函數來(lái)處理復制,那么我們沒(méi)有意義要去自定義拷貝構造函數呀,對,就普通情況而言這的確是沒(méi)有必要的,但在某寫(xiě)狀況下,類(lèi)體內的成員是需要開(kāi)辟動(dòng)態(tài)開(kāi)辟堆內存的,如果我們不自定義拷貝構造函數而讓系統自己處理,那么就會(huì )導致堆內存的所屬權產(chǎn)生混亂,試想一下,已經(jīng)開(kāi)辟的一端堆地址原來(lái)是屬于對象a的,由于復制過(guò)程發(fā)生,b對象取得是a已經(jīng)開(kāi)辟的堆地址,一旦程序產(chǎn)生析構,釋放堆的時(shí)候,計算機是不可能清楚這段地址是真正屬于誰(shuí)的,當連續發(fā)生兩次析構的時(shí)候就出現了運行錯誤。
為了更詳細的說(shuō)明問(wèn)題,請看如下的代碼。
//程序作者:管寧
//站點(diǎn):www.cndev-lab.com
//所有稿件均有版權,如要轉載,請務(wù)必著(zhù)名出處和作者
#include <iostream>
usingnamespacestd;
classInternet
{
public:
Internet(char*name,char*address)
{
cout<<"載入構造函數"<
strcpy(Internet::name,name);
strcpy(Internet::address,address);
cname=newchar[strlen(name)+1];
if(cname!=NULL)
{
strcpy(Internet::cname,name);
}
}
Internet(Internet &temp)
{
cout<<"載入COPY構造函數"<
strcpy(Internet::name,temp.name);
strcpy(Internet::address,temp.address);
cname=newchar[strlen(name)+1];//這里注意,深拷貝的體現!
if(cname!=NULL)
{
strcpy(Internet::cname,name);
}
}
~Internet()
{
cout<<"載入析構函數!";
delete[] cname;
cin.get();
}
voidshow();
protected:
charname[20];
charaddress[30];
char*cname;
};
voidInternet::show()
{
cout<<<":"<
<<
}
voidtest(Internet ts)
{
cout<<"載入test函數"<
}
voidmain()
{
Internet a("中國軟件開(kāi)發(fā)實(shí)驗室","www.cndev-lab.com");
Internet b =a;
b.show();
test(b);
}
上面代碼就演示了深拷貝的問(wèn)題,對對象b的cname屬性采取了新開(kāi)辟內存的方式避免了內存歸屬不清所導致析構釋放空間時(shí)候的錯誤,最后我必須提一下,對于上面的程序我的解釋并不多,就是希望讀者本身運行程序觀(guān)察變化,進(jìn)而深刻理解。
深拷貝和淺拷貝的定義可以簡(jiǎn)單理解成:如果一個(gè)類(lèi)擁有資源(堆,或者是其它系統資源),當這個(gè)類(lèi)的對象發(fā)生復制過(guò)程的時(shí)候,這個(gè)過(guò)程就可以叫做深拷貝,反之對象存在資源但復制過(guò)程并未復制資源的情況視為淺拷貝。
淺拷貝資源后在釋放資源的時(shí)候會(huì )產(chǎn)生資源歸屬不清的情況導致程序運行出錯,這點(diǎn)尤其需要注意!
以前我們的教程中討論過(guò)函數返回對象產(chǎn)生臨時(shí)變量的問(wèn)題,接下來(lái)我們來(lái)看一下在函數中返回自定義類(lèi)型對象是否也遵循此規則產(chǎn)生臨時(shí)對象!
先運行下列代碼:
//程序作者:管寧
//站點(diǎn):www.cndev-lab.com
//所有稿件均有版權,如要轉載,請務(wù)必著(zhù)名出處和作者
#include <iostream>
usingnamespacestd;
classInternet
{
public:
Internet()
{
};
Internet(char*name,char*address)
{
cout<<"載入構造函數"<
strcpy(Internet::name,name);
strcpy(Internet::address,address);
}
Internet(Internet &temp)
{
cout<<"載入COPY構造函數"<
strcpy(Internet::name,temp.name);
strcpy(Internet::address,temp.address);
cin.get();
}
~Internet()
{
cout<<"載入析構函數!";
cin.get();
}
protected:
charname[20];
charaddress[20];
};
Internet tp()
{
Internet b("中國軟件開(kāi)發(fā)實(shí)驗室","www.cndev-lab.com");
returnb;
}
voidmain()
{
Internet a;
a=tp();
}
從上面的代碼運行結果可以看出,程序一共載入過(guò)析構函數三次,證明了由函數返回自定義類(lèi)型對象同樣會(huì )產(chǎn)生臨時(shí)變量,事實(shí)上對象a得到的就是這個(gè)臨時(shí)Internet類(lèi)類(lèi)型對象temp的值。
這一下節的內容我們來(lái)說(shuō)一下無(wú)名對象。
利用無(wú)名對象初始化對象系統不會(huì )不調用拷貝構造函數。
那么什么又是無(wú)名對象呢?
很簡(jiǎn)單,如果在上面程序的main函數中有:
Internet ("中國軟件開(kāi)發(fā)實(shí)驗室","www.cndev-lab.com");
這樣的一句語(yǔ)句就會(huì )產(chǎn)生一個(gè)無(wú)名對象,無(wú)名對象會(huì )調用構造函數但利用無(wú)名對象初始化對象系統不會(huì )不調用拷貝構造函數!
下面三段代碼是很見(jiàn)到的三種利用無(wú)名對象初始化對象的例子。
//程序作者:管寧
//站點(diǎn):www.cndev-lab.com
//所有稿件均有版權,如要轉載,請務(wù)必著(zhù)名出處和作者
#include <iostream>
usingnamespacestd;
classInternet
{
public:
Internet(char*name,char*address)
{
cout<<"載入構造函數"<
strcpy(Internet::name,name);
}
Internet(Internet &temp)
{
cout<<"載入COPY構造函數"<
strcpy(Internet::name,temp.name);
cin.get();
}
~Internet()
{
cout<<"載入析構函數!";
cin.get();
}
public:
charname[20];
charaddress[20];
};
voidmain()
{
Internet a=Internet("中國軟件開(kāi)發(fā)實(shí)驗室","www.cndev-lab.com");
cout<
cin.get();
}
上面代碼的運行結果有點(diǎn)“出人意料”,從思維邏輯上說(shuō),當無(wú)名對象創(chuàng )建了后,是應該調用自定義拷貝構造函數,或者是默認拷貝構造函數來(lái)完成復制過(guò)程的,但事實(shí)上系統并沒(méi)有這么做,因為無(wú)名對象使用過(guò)后在整個(gè)程序中就失去了作用,對于這種情況c++會(huì )把代碼看成是:
Internet a("中國軟件開(kāi)發(fā)實(shí)驗室","www.cndev-lab.com");
省略了創(chuàng )建無(wú)名對象這一過(guò)程,所以說(shuō)不會(huì )調用拷貝構造函數。
最后讓我們來(lái)看看引用無(wú)名對象的情況。
//程序作者:管寧
//站點(diǎn):www.cndev-lab.com
//所有稿件均有版權,如要轉載,請務(wù)必著(zhù)名出處和作者
#include <iostream>
usingnamespacestd;
classInternet
{
public:
Internet(char*name,char*address)
{
cout<<"載入構造函數"<
strcpy(Internet::name,name);
}
Internet(Internet &temp)
{
cout<<"載入COPY構造函數"<
strcpy(Internet::name,temp.name);
cin.get();
}
~Internet()
{
cout<<"載入析構函數!";
}
public:
charname[20];
charaddress[20];
};
voidmain()
{
Internet &a=Internet("中國軟件開(kāi)發(fā)實(shí)驗室","www.cndev-lab.com");
cout<
cin.get();
}
引用本身是對象的別名,和復制并沒(méi)有關(guān)系,所以不會(huì )調用拷貝構造函數,但要注意的是,在c++看來(lái):
Internet &a=Internet("中國軟件開(kāi)發(fā)實(shí)驗室","www.cndev-lab.com");
是等價(jià)與:
Internet a("中國軟件開(kāi)發(fā)實(shí)驗室","www.cndev-lab.com");
的,注意觀(guān)察調用析構函數的位置(這種情況是在main()外調用,而無(wú)名對象本身是在main()內析構的)。
<<":"<
<<
聯(lián)系客服