拷貝構造函數與賦值構造函數(學習筆記)
什么時候用拷貝構造函數,和賦值構造函數:
(一)當用一個已初始化過了的自定義類類型對象去初始化另一個新構造的對象的時候,拷貝構造函數就會被自動調用。也就是說,當類的對象需要拷貝時,拷貝構造函數將會被調用。以下情況都會調用拷貝構造函數:
一個對象以值傳遞的方式傳入函數體
一個對象以值傳遞的方式從函數返回
一個對象需要通過另外一個對象進行初始化。
如果在類中沒有顯式地聲明一個拷貝構造函數,那么,編譯器將會自動生成一個默認的拷貝構造函數,該構造函數完成對象之間的位拷貝。位拷貝又稱淺拷貝,后面將進行說明。
自定義拷貝構造函數是一種良好的編程風格,它可以阻止編譯器形成默認的拷貝構造函數,提高源碼效率。
例子:
String a("hello");
String b("world");
String c = a; // 調用了拷貝構造函數,最好寫成 c(a);
c = b; // 調用了賦值構造函數。!
本例中第三個語句的風格較差,宜改寫成String c(a) 以區別於第四個語句。
(二)拷貝構造函數是在對象被創建時調用的,而賦值函數只能被已經存在了的對象調用。
不同點:
拷貝構造函數首先是一個構造函數,它調用的時候產生一個對象,是通過參數傳進來的那個對象來初始化,產生的對象。
operator=();是把一個對象賦值給一個原有的對象,所以如果原來的對象中有內存分配要先把內存釋放掉,而且還要檢查一下兩個對象是不是同一個對象,如果是的話就不做任何操作。
還要注意的是拷貝構造函數是構造函數,不返回值;而賦值函數需要返回一個對象自身的引用,以便賦值之后的操作。
淺拷貝和深拷貝
在某些狀況下,類內成員變量需要動態開辟堆內存,如果實行位拷貝,也就是把對象里的值完全復制給另一個對象,如A=B。這時,如果B中有一個成員變量指針已經申請了內存,那A中的那個成員變量也指向同一塊內存。這就出現了問題:當B把內存釋放了(如:析構),這時A內的指針就是野指針了,出現運行錯誤。
深拷貝和淺拷貝的定義可以簡單理解成:如果一個類擁有資源(堆,或者是其它系統資源),當這個類的對象發生復制過程的時候,這個過程就可以叫做深拷貝,反之對象存在資源,但復制過程並未復制資源的情況視為淺拷貝。
淺拷貝資源后在釋放資源的時候會產生資源歸屬不清的情況導致程序運行出錯。
下面是例子,注意看兩種函數怎么寫,什么地方用到:
#include <iostream>
using namespace std;
class CA
{
public:
CA(int b,char* cstr)
{
a=b;
str=new char[b];
strcpy(str,cstr);
}
CA(const CA& Other) //拷貝構造函數
{
a=Other.a;
str=new char[a]; //深拷貝
if(str!=0)
strcpy(str,Other.str);
}
CA & operator = (const CA& Other) //賦值符重載
{
a=Other.a; //復制常規成員
// (1) 檢查自賦值
if(this == &Other)
return *this;
// (2) 釋放原有的內存資源
delete [] str;
// (3) 分配新的內存資源,並復制內容
str = new char[a];
strcpy(str,Other.str);
// (4) 返回本對象的引用
return *this;
}
void Show()
{
cout<<str<<endl;
}
~CA()
{
delete str;
}
private:
int a;
char *str;
};
int main()
{
CA A(10,"Hello!");
CA B=A;
B.Show();
CA C(9,"world!");
C = B;
return 0;
}
下面是另外一個例子,修改了下里面的賦值構造函數,把temp變量去掉了,這樣更省空間吧?
class CExample
{
...
CExample(const CExample&); //拷貝構造函數
CExample& operator = (const CExample&); //賦值符重載
...
};
//拷貝構造函數使用賦值運算符重載的代碼。
CExample::CExample(const CExample& RightSides)
{
pBuffer=NULL;
*this=RightSides //調用重載后的"="
}
//賦值操作符重載
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=temp; //建立新指向
return *this
}
//其實也沒啥改進,但覺得這樣的寫法是不是更好?省了一個temp臨時變量。
CExample & CExample::operator = (const CExample& RightSides)
{
nSize=RightSides.nSize; //復制常規成員
delete []pBuffer; //刪除原指針指向內容 (將刪除操作放在后面,避免X=X特殊情況下,內容的丟失)
pBuffer=new char[nSize]; //建立新指向
memcpy(pBuffer,RightSides.pBuffer,nSize*sizeof(char));
return *this
}
