拷貝構造函數:
用一個已經存在的對象來生成一個相同類型的新對象。(淺拷貝)
預設的拷貝構造函數:
如果自定義了拷貝構造函數,編譯器就不在生成預設的拷貝構造函數。
如果沒有自定義拷貝構造函數,但在代碼中用到了拷貝構造函數,編譯器會生成預設的拷貝構造函數。
深拷貝&淺拷貝:
系統預設的拷貝構造函數是淺拷貝,類中含有指針類型的變量,須自定義拷貝構造函數用深拷貝來實作。
淺拷貝隻是對指針的拷貝,拷貝後兩個指針指向同一個記憶體空間,所指向的空間内容并沒有複制,而是由兩個對象共用。深拷貝不但對指針進行拷貝,而且對指針指向的内容進行拷貝,經深拷貝後的指針是指向兩個不同位址的指針。
如圖:
思考:
當對象中存在指針成員時,為什麼需要自己實作拷貝構造函數?如果不,會出現怎樣的問題?
看代碼:
#include<iostream>
class CGoods
{
public:
CGoods(const char* name, double price, int amount)//帶有三個參數的構造函數
{
std::cout << this << " :CGoods::CGoods(char*,float,int)" << std::endl;
mname = new char[strlen(name) + 1]();
strcpy(mname, name);
mprice = price;
mamount = amount;
}
CGoods()//不帶參數的構造函數
{
std::cout << this << " :CGoods::CGoods()" << std::endl;
mname = new char[1]();
}
~CGoods()
{
std::cout << this << " :CGoods::~CGoods()" << std::endl;
delete[] mname;
}
private:
char* mname;
double mprice;
int mamount;
};
int main()
{
CGoods good1("car1", 10.1, 10);
CGoods good2 = good1;
return 0;
}
程式運作結果:調一次構造函數,調兩次析構函數。
分析:兩個對象的指針成員所指記憶體相同,這會導緻什麼問題呢?
mname指針被配置設定一次記憶體,但是程式結束時該記憶體卻被釋放了兩次,會造成記憶體洩漏問題,這是一個不容忽視的問題。我們不得不自己寫一個拷貝構造函數。
CGoods(const CGoods &rhs)
{
std::cout << this << " :CGoods::CGoods(const CGoods &rhs)" << std::endl;
mname = new char[strlen(rhs.mname) + 1]();
strcpy(mname, rhs.mname);
mprice = rhs.mprice;
mamount = rhs.mamount;
}
程式運作結果
注意:拷貝構造函數傳參必須傳引用。若正常傳,則會不斷地遞歸去生成新形參對象,最後導緻棧溢出。也不能用*,若寫成 CGoods(const CGoods* rhs),就會變成一個構造函數,CGoods*傳的是已存在對象的位址。
指派運算符的重載函數:
用一個已存在的對象指派給相同類型的已存在對象。(淺拷貝)
預設指派運算符的重載函數:
指派運算符重載函數用于類對象的指派操作,當我們未實作該函數時,編譯器會自動為我們實作該函數。同拷貝構造函數一樣,系統預設的指派運算符重載函數是淺拷貝,類中含有指針類型的變量,須自定義指派運算符重載函數用深拷貝來實作。
CGoods& operator=(const CGoods& rhs)
{
std::cout << this << " CGoods::operator=(const CGoods&)" << std::endl;
if (this != &rhs)//判斷是否給自己指派
{
delete[] mname;//防止記憶體洩漏
mname = new char[strlen(rhs.mname) + 1]();
strcpy(mname, rhs.mname);
mprice = rhs.mprice;
mamount = rhs.mamount;
}
return *this;
}
int main()
{
CGoods good1("car1", 10.1, 10);
CGoods good2;
good2 = good1;
return 0;
}
程式運作結果:
思考:
為什麼要避免自指派呢?1)自己給自己指派完全是毫無意義,為了效率。
2)如果類的資料成員中含有指針,自指派有時會導緻災難性的後果。對于指針間的指派,先要将p所指向的空間delete掉,然後再為p重新配置設定空間,将被拷貝指針所指的内容拷貝到p所指的空間。如果是自指派,那麼p和被拷貝指針是同一指針,在指派操作前對p的delete操作,将導緻p所指的資料同時被銷毀。
拷貝構造函數與指派函數的差別?
在看到“=”操作符為對象指派的時候,
如果是在對象定義時(Test B = (Test)c),此時調用拷貝構造函數;
如果不是在對象定義指派時(B = c),此時調用指派函數。
注:構造函數、拷貝構造函數,帶有構造兩個字,顧名思義,就是在對象聲明或定義時才會使用。
拷貝構造函數與指派函數定義的差別?
記憶體空間角度:
1)拷貝構造函數的使用,是在建立對象時;當時對象沒有占有記憶體,故不需要釋放記憶體,不重建立立記憶體空間。
2)指派函數的使用,是在對象建立後;當時對象已經占有記憶體,故需要釋放先前記憶體,然後重新擷取記憶體空間。
下面我們來看一個題目:
class String
{
public:
String(const char *str = NULL); // 普通構造函數
String(const String &other); // 拷貝構造函數
~String(void); // 析構函數
String & operator = (const String &other); // 指派函數
private:
char *m_data; // 用于儲存字元串
};
各個解析分别完成下上面String類聲明的函數:
1、構造函數
/*
1、構造函數在構造對象時使用;
2、傳入參數的判斷;
3、對象的初始化問題。
*/
String::String(const char *str)
{
if ( NULL == str)
{
m_data = new char[1]
*m_data = '\0';
}
else
{
int len = strlen(str);
m_data = new char[len + 1];
strcpy(m_data,str);
}
}
2、拷貝構造函數
/*
1、拷貝構造函數必須在構造對象時使用,即定義對象時;
2、對象初始化問題。
*/
String::String(const String &other)
{
int len = strlen(other.m_data);
m_data = new char[len+1];
strcpy(m_data,other.m_data);
}
3、指派函數
/*
1、指派函數使用時,對象肯定已經建立;
2、指派前,判斷是否是自我指派;
3、指派前,記憶體空間的準備:
由于指派前,對象已占有一定大小記憶體,但是指派對象所占記憶體大小與
對象已占的記憶體大小不一定一緻;
先釋放對象已占的記憶體,然後配置設定心記憶體。
4、正常指派
*/
String & String::operator = (const String &other)
{
if (&other == this)
{
return *this;
}
delete [] m_data;
int len = strlen(other.m_data);
m_data = new char[len+1];
strcpy(m_data,other.m_data);
return *this;
}
4、析構函數
/*
資源的釋放
*/
String::~String(void)
{
delete []m_data;
}