拷貝構造函數說明
定義
拷貝構造函數,是一種特殊的構造函數,它由編譯器調用來完成一些基於同一類的其他對象的構建及初始化。其唯一的參數(對象的引用)是不可變的(const類型)。此函數經常用在函數調用時用戶定義類型的值傳遞及返回。拷貝構造函數要調用基類的拷貝構造函數和成員函數。如果可以的話,它將用常量方式調用,另外,也可以用非常量方式調用。
調用拷貝構造函數的情形
在C++中,下面三種對象需要調用拷貝構造函數:
1) 一個對象以值傳遞的方式傳入函數體;
2) 一個對象以值傳遞的方式從函數返回;
3) 一個對象需要通過另外一個對象進行初始化;
如果在前兩種情況不使用拷貝構造函數的時候,就會導致一個指針指向已經被刪除的內存空間。對於第三種情況來說,初始化和賦值的不同含義是構造函數調用的原因。事實上,拷貝構造函數是由普通構造函數和賦值操作符共同實現的。
拷貝構造函數不可以改變它所引用的對象,其原因如下:當一個對象以值傳遞的方式傳給一個函數的時候,拷貝構造函數自動的被調用來生成函數中的對象。如果一個對象是被傳入自己的拷貝構造函數,它的拷貝構造函數將會被調用來拷貝這個對象這樣複製纔可以傳入它自己的拷貝構造函數,這會導致無限循環直至棧溢出(Stack Overflow)。除了當對象傳入函數的時候被隱式調用以外,拷貝構造函數在對象被函數返回的時候也同樣的被調用。
隱式的拷貝構造函數
如果在類中沒有顯式的聲明一個拷貝構造函數,那麼,編譯器會自動生成一個來進行對象之間的位拷貝(Bitwise Copy)。這個隱含的拷貝構造函數簡單的關聯了所有的類成員。注意到這個隱式的拷貝構造函數和顯式聲明的拷貝構造函數的不同在於對成員的關聯方式。顯式聲明的拷貝構造函數關聯的只是被實例化的類成員的缺省構造函數,除非另外一個構造函數在類初始化或構造列表的時候被調用。
拷貝構造函數使程序更有效率,因爲它不用再構造一個對象的時候改變構造函數的參數列表。設計拷貝構造函數是一個良好的風格,即使是編譯系統會自動爲你生成默認拷貝構造函數。事實上,默認拷貝構造函數可以應付許多情況。
拷貝構造函數,顧名思義,等於拷貝+ 構造。它肩負着創建新對象的任務,同時還要負責把另外一個對象拷貝過來。比如下面的情況就調用拷貝構造函數:
CString str = strOther;
賦值操作則只含有拷貝的意思,也就是說對象必須已經存在。比如下面的情況會調用賦值操作。
str = strOther;
拷貝構造函數對同一個對象來說只會調用一次,而且是在對象構造時調用。此時對象本身還沒有構造,無需要去釋放自己的一些資源。而賦值操作可能會調用多次,你在拷貝之前要釋放自己的一些資源,否則會造成資源泄露。
示例
以下討論中將用到的例子:
class CExample
{
public:
CExample(){pBuffer=NULL; nSize=0;}
~CExample(){delete pBuffer;}
void Init(int n){ pBuffer=new char[n]; nSize=n;}
private:
char *pBuffer; //類的對象中包含指針,指向動態分配的內存資源
int nSize;
};
這個類的主要特點是包含指向其他資源的指針。
pBuffer指向堆中分配的一段內存空間。
int main(int argc, char* argv[])
{
CExample theObjone;
theObjone.Init(40);
//現在需要另一個對象,需要將他初始化稱對象一的狀態
CExample theObjtwo=theObjone;
...
}
語句"CExample theObjtwo=theObjone;"用theObjone初始化theObjtwo。
其完成方式是內存拷貝,複製所有成員的值。
完成後,theObjtwo.pBuffer==theObjone.pBuffer。
即它們將指向同樣的地方,指針雖然複製了,但所指向的空間並沒有複製,而是由兩個對象共用了。這樣不符合要求,對象之間不獨立了,併爲空間的刪除帶來隱患。所以需要採用必要的手段來避免此類情況。
回顧一下此語句的具體過程:首先建立對象theObjtwo,並調用其構造函數,然後成員被拷貝。
可以在構造函數中添加操作來解決指針成員的問題。
所以C++語法中除了提供缺省形式的構造函數外,還規範了另一種特殊的構造函數:拷貝構造函數,上面的語句中,如果類中定義了拷貝構造函數,這對象建立時,調用的將是拷貝構造函數,在拷貝構造函數中,可以根據傳入的變量,複製指針所指向的資源。
拷貝構造函數的格式爲:構造函數名(對象的引用)
提供了拷貝構造函數後的CExample類定義爲:
class CExample
{
public:
CExample(){pBuffer=NULL; nSize=0;}
~CExample(){delete pBuffer;}
CExample(const CExample&); //拷貝構造函數
void Init(int n){ pBuffer=new char[n]; nSize=n;}
private:
char *pBuffer; //類的對象中包含指針,指向動態分配的內存資源
int nSize;
};
CExample::CExample(const CExample& RightSides) //拷貝構造函數的定義
{
nSize=RightSides.nSize; //複製常規成員
pBuffer=new char[nSize]; //複製指針指向的內容
memcpy(pBuffer,RightSides.pBuffer,nSize*sizeof(char));
}
這樣,定義新對象,並用已有對象初始化新對象時,CExample(const CExample& RightSides)將被調用,而已有對象用別名RightSides傳給構造函數,以用來作複製。原則上,應該爲所有包含動態分配成員的類都提供拷貝構造函數。
下面介紹拷貝構造函數的另一種調用。當對象直接作爲參數傳給函數時,函數將建立對象的臨時拷貝,這個拷貝過程也將調同拷貝構造函數。例如:
BOOL testfunc(CExample obj);
testfunc(theObjone); //對象直接作爲參數。
BOOL testfunc(CExample obj)
{
//針對obj的操作實際上是針對複製後的臨時拷貝進行的
}
還有一種情況,也是與臨時對象有關的
當函數中的局部對象被被返回給函數調者時,也將建立此局部對象的一個臨時拷貝,拷貝構造函數也將被調用
CTest func()
{
CTest theTest;
return theTest;
}
二、值的重載 下面的代碼與上例相似
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)
{
if (this == &RightSides) // 如果自己給自己賦值則直接返回
{
return *this;
}
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; //調用重載後的"="
}
四、拷貝構造與構造區別 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;
}