C++拷貝函式詳解
1.什麼是拷貝建構函式:
CA(const CA& C)就是我們自定義的拷貝建構函式。可見,拷貝建構函式是一種特殊的建構函式,函式的名稱必須和類名稱一致,它的唯一的一個引數是本型別的一個引用變數,該引數是const型別,不可變的。例如:類X的拷貝建構函式的形式為X(X& x)。當用一個已初始化過了的自定義類型別物件去初始化另一個新構造的物件的時候,拷貝建構函式就會被自動呼叫。也就是說,當類的物件需要拷貝時,拷貝建構函式將會被呼叫。以下情況都會呼叫拷貝建構函式:
①程式中需要新建立一個物件,並用另一個同類的物件對它初始化,如前面介紹的那樣。
②當函式的引數為類的物件時。在呼叫函式時需要將實參物件完整地傳遞給形參,也就是需要建立一個實參的拷貝,這就是按實參複製一個形參,系統是通過呼叫複製建構函式來實現的,這樣能保證形參具有和實參完全相同的值。
③函式的返回值是類的物件。在函式呼叫完畢將返回值帶回函式呼叫處時。此時需要將函式中的物件複製一個臨時物件並傳給該函式的呼叫處。如
Box f( ) //函式f的型別為Box類型別
{Box box1(12,15,18);
return box1; //返回值是Box類的物件
}
int main( )
{Box box2; //定義Box類的物件box2
box2=f( ); //呼叫f函式,返回Box類的臨時物件,並將它賦值給box2
}
如果在類中沒有顯式地宣告一個拷貝建構函式,那麼,編譯器將會自動生成一個預設的拷貝建構函式,該建構函式完成物件之間的位拷貝。位拷貝又稱淺拷貝,後面將進行說明。
淺拷貝和深拷貝 在某些狀況下,類內成員變數需要動態開闢堆記憶體,如果實行位拷貝,也就是把物件裡的值完全複製給另一個物件,如A=B。這時,如果B中有一個成員變數指標已經申請了記憶體,那A中的那個成員變數也指向同一塊記憶體。這就出現了問題:當B把記憶體釋放了(如:析構),這時A內的指標就是野指標了,出現執行錯誤。 深拷貝和淺拷貝可以簡單理解為:如果一個類擁有資源,當這個類的物件發生複製過程的時候,資源重新分配,這個過程就是深拷貝,反之,沒有重新分配資源,就是淺拷貝。
2.C++拷貝建構函式的幾個細節
1) 以下函式哪個是拷貝建構函式,為什麼?
1.X::X( const X&);
2.X::X(X);
3.X::X(X&, int a=1);
4.X::X(X&, int a=1, b=2);
解答:1)
對於一個類X,如果一個建構函式的第一個引數是下列之一:
a) X&
b) const X&
c) volatile X&
d) const volatile X&
且沒有其他引數或其他引數都有預設值,那麼這個函式是拷貝建構函式.
1.X::X( const X&); //是拷貝建構函式
2.X::X(X&, int =1); //是拷貝建構函式
2) 一個類中可以存在多於一個的拷貝建構函式嗎?
解答:類中可以存在超過一個拷貝建構函式,
1.class X {
2.public :
3. X( const X&);
4. X(X&); // OK
5.};
注意,如果一個類中只存在一個引數為X&的拷貝建構函式,那麼就不能使用const X或volatile X的物件實行拷貝初始化.
1.class X {
2.public :
3. X();
4. X(X&);
5.};
6.
7.const X cx;
8.X x = cx; // error
如果一個類中沒有定義拷貝建構函式,那麼編譯器會自動產生一個預設的拷貝建構函式.
這個預設的引數可能為X::X(const X&)或X::X(X&),由編譯器根據上下文決定選擇哪一個.
預設拷貝建構函式的行為如下:
預設的拷貝建構函式執行的順序與其他使用者定義的建構函式相同,執行先父類後子類的構造.
拷貝建構函式對類中每一個數據成員執行成員拷貝(memberwise Copy)的動作.
a)如果資料成員為某一個類的例項,那麼呼叫此類的拷貝建構函式.
b)如果資料成員是一個數組,對陣列的每一個執行按位拷貝.
c)如果資料成員是一個數量,如int,double,那麼呼叫系統內建的賦值運算子對其進行賦值.
3) 拷貝建構函式不能由成員函式模版生成.
struct X {
template < typename T>
X( const T& ); // NOT copy ctor, T can't be X
template < typename T>
operator=( const T& ); // NOT copy ass't, T can't be X
};
原因很簡單, 成員函式模版並不改變語言的規則,而語言的規則說,如果程式需要一個拷貝建構函式而你沒有宣告它,那麼編譯器會為你自動生成一個. 所以成員函式模版並不會阻止編譯器生成拷貝建構函式, 賦值運算子過載也遵循同樣的規則
3.拷貝建構函式與賦值函式的異同:
1) 拷貝構造,是一個的物件來初始化一片記憶體區域,這片記憶體區域就是你的新物件的記憶體區域賦值運算,對於一個已經被初始化的物件來進行operator=操作
class A;
A a;
A b=a; //拷貝建構函式呼叫
//或
A b(a); //拷貝建構函式呼叫
///////////////////////////////////
A a;
A b;
b =a; //賦值運算子呼叫
你只需要記住,在C++語言裡,
String s2(s1);
String s3 = s1;
只是語法形式的不同,意義是一樣的,都是定義加初始化,都呼叫拷貝建構函式。
2)一般來說是在資料成員包含指標物件的時候,應付兩種不同的處理需求的一種是複製指標物件,一種是引用指標物件 copy大多數情況下是複製,=則是引用物件的
例子:
class A
{
int nLen;
char * pData;
}
顯然
A a, b;
a=b的時候,對於pData資料存在兩種需求
第一種copy
a.pData = new char [nLen];
memcpy(a.pData, b.pData, nLen);
另外一種(引用方式):
a.pData = b.pData
通過對比就可以看到,他們是不同的
往往把第一種用copy使用,第二種用=實現
你只要記住拷貝建構函式是用於類中指標,物件間的COPY
3)拷貝建構函式首先是一個建構函式,它呼叫的時候產生一個物件,是通過引數傳進來的那個物件來初始化,產生的物件。
operator=();是把一個物件賦值給一個原有的物件,所以如果原來的物件中有記憶體分配要先把記憶體釋放掉,而且還要檢查一下兩個物件是不是同一個物件,如果是的話就不做任何操作。
還要注意的是拷貝建構函式是建構函式,不返回值
而賦值函式需要返回一個物件自身的引用,以便賦值之後的操作
4) 在形式上
類名(形參表列); //普通建構函式的宣告,如Box(int h,int w,int len);
類名(類名& 物件名); //複製建構函式的宣告,如Box(Box &b);
5) 在建立物件時,實參型別不同。系統會根據實參的型別決定呼叫普通建構函式或複製建構函式。如:
Box box1(12,15,16); //實參為整數,呼叫普通建構函式
Box box2(box1); //實參是物件名,呼叫複製建構函式