目錄
1.拷貝構造函數
首先,簡單介紹一下拷貝構造函數。拷貝構造函數是構造函數的一種,它在創建對象時,是使用同一類中之前創建的對象來初始化新創建的對象。
拷貝構造函數將創建好(已初始化)的對象作爲參數,返回一個新的對象。
如果我們沒有定義拷貝構造函數,系統會自動生成一個默認的拷貝構造函數。
拷貝構造函數的一般形式如下所示:
ClassName(const ClassName& obj)
{}
我們呢來看一個實例:
class People
{
public:
//構造函數
People(int age, string name) :_age(age), _name(name)
{}
// 拷貝構造函數
People(const People& obj)
{
_age = obj._age;
_name = obj._name;
cout << "拷貝構造調用" << endl;
}
void Print()
{
cout << _name << ":" << _age<< endl;
}
private:
int _age;
string _name;
};
int main()
{
People xiaoMing(14, "小明");
People xiaoHong(xiaoMing);
xiaoMing.Print();
xiaoHong.Print();
return 0;
}
打印結果如下:
可以發現小明的屬性已經拷貝給了小紅,那麼如果我們刪除掉上述代碼中的拷貝構造函數,結果會怎麼樣?答案是:結果是一樣的,這是爲什麼呢,這是因爲系統會生成一個默認的拷貝構造函數,來進行對象初始化對象的操作,但是這種拷貝構造函數只能用於淺拷貝,不能用於深拷貝。接下來我們引出淺拷貝和身拷貝。
2.淺拷貝和深拷貝
假設類中的成員變量有一個指針,我們在類的構造函種爲這個指針在堆上申請了內存。如果我們用這個類取初始化其他類會發生什麼情況?
我們先來看一個實例:
class People
{
public:
//構造函數
People(int age, string name, int size = 3) :_age(age), _name(name),_size(size)
{
_arr = new int[size];
}
~People()
{
delete[] _arr;
}
void Print()
{
cout << _name << ":" << _age<< endl;
}
private:
int _age;
string _name;
int _size;
int* _arr;
};
int main()
{
People xiaoMing(14, "小明");
People xiaoHong(xiaoMing);
return 0;
}
我們在People類中加了兩個變量,一個_size,一個int的指針,然後在析構函數種將_arr的內存釋放。執行會發現上述代碼會報錯,這是爲什麼呢?
如上圖,小明首先在堆上有三個字節的大小,通過一個默認的拷貝構造函數(淺拷貝),小紅的_arr指針也指向了這塊空間,當程序結束的時候,首先調用小明的析構函數釋放了這塊空間,被釋放掉了,小紅的析構函數又取釋放這塊被釋放掉的空間,所以程序會報錯。
總結一下:
淺拷貝只是複製指向某些對象的指針,並不會對所指內容進行復制。如上:淺拷貝只是把小名的_arr指針指向了小明的_arr指針,並沒又進行空間的賦值。當程序結束的時候,不同對象的析構函數會釋放同一塊內存,報錯。
而深拷貝,通過寫拷貝構造函數,使堆上的空間也複製一份,當程序結束的時候,不同對象的析構函數會釋放的內存雖然內容一樣,但是地址不一樣,所以不會報錯。
在上述的代碼種我們增加拷貝構造函數如下之後,程序不會報錯。
People(const People& obj)
{
_age = obj._age;
_name = obj._name;
_size = obj._size;
_arr = new int[_size];
cout << "拷貝構造調用" << endl;
}
3.拷貝構造函數的參數能用值傳遞嗎?
右函數的傳參過程我們可以得知,如果函數的參數不是指針或者引用的時候,我們在傳參的時候,會將實參複製給形參。
如果我們將拷貝構造函數的參數去掉引用,那麼在調用拷貝構造函數的時候,首先會申請一個新的對象,然後用傳入的實參去初始化這個新的對象,這個時候還會調用到我們的拷貝構造函數,如此層層調用,形成無限的遞歸。
因此,拷貝構造函數的參數必須用引用或者指針。