一、 拷貝建構函式與建構函式的區別:
拷貝建構函式,是一種特殊的建構函式,它由編譯器調用來完成一些基於同一類的其他對象的構建及初始化。其唯一的參數(對象的引用)是不可變的(const類型)--參考分析1。
拷貝建構函式與建構函式的區別:
class 類名
{
public:
類名(形參參數)//建構函式
類名(類名&對象名)//拷貝建構函式
,,,,,,,,,,,,,,,,,,,,,
};
拷貝建構函式的實現:
類名::類名(類名&對象名)//拷貝建構函式的實現
{函數體}
拷貝建構函式:
Class Point
{
Public:
Point(int xx=0,int yy=m)(X=xx;Y=yy;)
Point(Point& p);
Int getX() {return X;}
Int getY(){ return Y;}
Private :
Int X,Y;
}
Point::Point(Point& p)
{
X=p.X;
Y=p.Y;
Cout<<"拷貝建構函式調用"<<endl;
}
二、拷貝建構函式介紹:
在C++中,下面三種對象需要調用拷貝建構函式:
1) 對象以值傳遞的方式傳入函數體;
2) 對象以值傳遞的方式從函數返回;
3) 對象需要通過另外一個對象進行初始化;
當使用已初始化對象去初始化另一個新對象的時候,拷貝建構函式就會被自動調用,如果沒有自訂拷貝建構函式的時候系統將會提供預設的拷貝建構函式來完成這個過程;
如果類體內的成員是需要開闢動態開闢堆記憶體的,而不自訂深拷貝建構函式,就會導致堆記憶體的所屬權產生混亂,兩個對象的指標指向同一個堆地址,一旦程式產生析構,釋放堆的時候,電腦是不可能清楚這段地址是真正屬於誰的,當連續發生兩次析構的時候就出現了運行錯誤。
#include <iostream>usingnamespacestd; classInternet { public: Internet(char*name,char*address) { cout<<"載入建構函式"<<endl; strcpy(Internet::name,name);strcpy(Internet::address,address); cname=new char[strlen(name)+1]; if(cname!=NULL) { strcpy(Internet::cname,name); } } Internet(Internet &temp) { cout<<"載入COPY建構函式"<<endl; strcpy(Internet::name,temp.name); strcpy(Internet::address,temp.address); cname=new char[strlen(name)+1];//這裡注意,深拷貝的體現! if(cname!=NULL) { strcpy(Internet::cname,name); } } ~Internet() { cout<<"載入解構函式!"<<endl; delete[] cname; cin.get(); } voidshow(); protected: charname[20]; charaddress[30]; char*cname; }; void Internet::show() { cout<<"show 函數"<<endl;}voidtest(Internet ts) { cout<<"載入test函數"<<endl;}voidmain() { Internet a("中國軟體開發實驗室","www.cndev-lab.com"); Internet b =a; b.show(); test(b); } |
上面代碼就示範了深拷貝的問題,對對象b的cname屬性採取了新開闢記憶體的方式避免了記憶體歸屬不清所導致析構釋放空間時候的錯誤,最後我必須提一下,對於上面的程式我的解釋並不多,就是希望讀者本身運行程式觀察變化,進而深刻理解。
深拷貝和淺拷貝的定義可以簡單理解成:
如果一個類擁有資源(堆,或者是其它系統資源),當這個類的對象發生複製過程(新開闢記憶體的方式)的時候,這個過程就可以叫做深拷貝;
反之對象存在資源但複製過程並未複製資源(僅僅複製對象成員的指標,未新開闢記憶體)的情況視為淺拷貝。
三、拷貝建構函式與賦值運算子:
事實上,拷貝建構函式是由普通建構函式和賦值操作符共同實現的。
賦值符的重載 :
下面的代碼與上例相似
int main(int argc, char* argv[])
{
CExample theObjone;
theObjone.Init(40);
CExample theObjthree;
theObjthree.Init(60);
//現在需要一個對象賦值操作,被賦值對象的原內容被清除,並用右邊對象的內容填充。
theObjthree=theObjone;
return 0;
}
也用到了"="號,但與"一、"中的例子並不同,"一、"的例子中,"="在對象聲明語句中,表示初始化。更多時候,這種初始化也可用括弧表示。
例如 CExample theObjone(theObjtwo);
而本例子中,"="表示賦值操作。將對象theObjone的內容複寫到對象theObjthree;,這其中涉及到對象theObjthree原有內容的丟棄,新內容的複製。
但"="的預設操作只是將成員變數的值相應複製。舊的值被自然丟棄。
由於對象內包含指標,將造成不良後果:指標的值被丟棄了,但指標指向的內容並未釋放。指標的值被複製了,但指標所指內容並未複製。
因此,包含動態分配成員的類除提供拷貝建構函式外,還應該考慮重載"="賦值操作符號。
類定義變為:
class CExample
{
...
CExample(const CExample&); //拷貝建構函式
CExample& operator = (const CExample&); //賦值符重載
...
};
//賦值操作符重載
CExample & CExample::operator = (const CExample& RightSides)
{
nSize=RightSides.nSize; //複製常規成員
char *temp=new char[nSize]; //複製指標指向的內容
memcpy(temp,RightSides.pBuffer,nSize*sizeof(char));
delete []pBuffer; //刪除原指標指向內容 (將刪除操作放在後面,避免X=X特殊情況下,內容的丟失)
pBuffer=NULL;
pBuffer=temp; //建立新指向
return *this
}
四、注意事項
常見錯誤:
拷貝建構函式使用賦值運算子多載的代碼。
CExample::CExample(const CExample& RightSides)
{
*this=RightSides //調用重載後的"="
}
而這樣會造成迴圈調用重載後的"="和拷貝建構函式(因為對象以值傳遞的方式傳入函數體會調用拷貝建構函式),最後造成棧溢出(Stack Overflow)。
****分析:*********************************************
1、 拷貝建構函式不可以改變它所引用的對象,其原因如下:當一個對象以傳遞值的方式傳入函數的時候,拷貝建構函式自動的被調用來產生函數中的對象(如果拷貝函數可以改變並且改變了引用對象,則建立的新對象與引用對象不一致,未能達到拷貝目的)。
2、如果一個對象是被傳入自己的拷貝建構函式,它的拷貝建構函式將會被調用來拷貝這個對象這樣複製才可以傳入它自己的拷貝建構函式,這會導致無限迴圈直至棧溢出(Stack Overflow)。除了當對象傳入函數的時候被隱式調用以外,拷貝建構函式在對象被函數返回的時候也同樣的被調用。
**************************************************