C++入門知識-拷貝構造函數-淺拷貝、深拷貝

一、概念

拷貝構造函數特徵:第一個參數是自身類類型的引用,且任何額外參數都有默認值

class Foo
{
public:
	Foo(); // 默認構造函數
	Foo(const Foo &); // 拷貝構造函數
	//...
};

注意
如果沒有爲一個類定義拷貝構造函數,編譯器會生成一個默認的拷貝構造函數默認的拷貝構造函會依次將非static成員拷貝到正在創建的對象中(對於基本類型的成員變量,按字節複製;對於類類型成員變量,調用其相應類型的拷貝構造函數);
但是,當你的類含有指針類型的私有數據成員時,默認拷貝構造函數是危險的,因爲它使兩個對象的指針都指向了同一塊內存區域,這時便是淺拷貝了。

二、什麼時候會觸發拷貝構造函數

  1. 使用同類型的對象 去創建另外一個對象;
    string nines = string(100,'9');
    
  2. 把對象傳入函數的非引用參數時;
    void Function(string s) //調用這個函數時,實參傳到這個形參s時,會觸發拷貝構造
    {
    	// ...
    }
    
  3. 把對象作爲函數非引用返回值時。
    string s = "hello"
    string Function()
    {
    	// ...
    	return s; // 觸發拷貝構造
    }
    

三、淺拷貝

淺拷貝小故事
張三有 100 元現金和一個房產證,房產證上寫着房子的地址“魔都1巷007號”;
某天,張三就想把自己的東西複製一份給李四;
於是就複製了 100 元和一個房產證給李四,房產證上的地址還是寫着“魔都1巷007號”。
後來,張三把房子賣了,李四某天也去賣房子的時候就奔潰了。

淺拷貝
就是在拷貝對象時,只是按字節地拷貝了類成員的值,對於指針或引用類型,沒有拷貝其指向地具體數據過去;

就像上面故事中,只拷貝了100元和房產證,而兩份房產證都是指向同一份數據(“魔都1巷007號”)。

用代碼的形式表示

#include <cstdio>
#include <cstring>

#define HOUSE_LEN 64
class Person
{
	int money;		// 錢
	char *house; 	// 房子
public:
	Person(int _money, char *_house)
	{
		money = _money;
		house = _house;
		
	}
	
	void Show()
	{
		printf("I have %d Yuan,and house %s\n",money, house);
	}
	
	void SellHouse()// 賣房子
	{
		delete[] house;
	}
};

int main()
{
	char *pHouse = new char[HOUSE_LEN];
	strncpy(pHouse, "魔都1巷007號", HOUSE_LEN);
	
	Person ZhangSan(100, pHouse);
	printf("I am ZhangSan\n");
	ZhangSan.Show();
	
	Person LiSi = ZhangSan;
	printf("I am LiSi\n");
	LiSi.Show();
	
	ZhangSan.SellHouse();
	printf("ZhangSan SellHouse\n");
	
	//LiSi.SellHouse();  // 執行這一句會使程序報錯,double free
	//printf("LiSi SellHouse\n");
	return 0;
}

淺拷貝的隱患
多個對象共用同一塊資源,同一塊資源釋放多次,崩潰或者內存泄漏。

四、深拷貝

深拷貝小故事
張三有 100 元現金和一個房產證,房產證上寫着房子的地址“魔都1巷007號”;
某天,張三就想把自己的東西複製一份給李四;
於是就複製了 100 元和一個房產證給李四,並且新建了一座房子“魔都1巷007號-2”,把李四的房產證上地址改爲“魔都1巷007號-2”。

深拷貝
就是在拷貝對象時,對於指針或引用類型的成員,需要申請新的內存並把源對象成員指向的數據一起拷貝到新的內存。

就像上面故事中,拷貝了100元和房產證,並且新建了“魔都1巷007號-2”,把李四的房產證地址也改爲“魔都1巷007號-2”。

用代碼的形式表示

#include <cstdio>
#include <cstring>

#define HOUSE_LEN 64
class Person
{
	int money;		// 錢
	char *house; 	// 房子
public:
	Person(int _money, char *_house)
	{
		money = _money;
		house = _house;
		
	}
	Person(const Person &person)
	{
		money = person.money;
		house = new char[HOUSE_LEN];
		snprintf(house,HOUSE_LEN, "%s-2",person.house);
	}
	void Show()
	{
		printf("I have %d Yuan,and house %s\n",money, house);
	}
	
	void SellHouse()// 賣房子
	{
		delete[] house;
	}
};

int main()
{
	char *pHouse = new char[HOUSE_LEN];
	strncpy(pHouse, "魔都1巷007號", HOUSE_LEN);
	
	Person ZhangSan(100, pHouse);
	printf("I am ZhangSan\n");
	ZhangSan.Show();
	
	Person LiSi = ZhangSan;
	printf("I am LiSi\n");
	LiSi.Show();
	
	ZhangSan.SellHouse();
	printf("ZhangSan SellHouse\n");
	
	LiSi.SellHouse();  // 因爲有了拷貝構造函數做了深拷貝,這裏不會報錯
	printf("LiSi SellHouse\n");
	return 0;
}

總結

在設計一個類的過程中,當這個類有指針或引用類型的成員時,且可能會觸發調用拷貝構造函數,就需要自己定義一個拷貝構造函數,避免編譯器的默認的拷貝構造函數引起淺拷貝而導致程序面臨危險。

參考資料

《C++Primer》
C++細節 深拷貝和淺拷貝(位拷貝)詳解

如果對你有幫助的話,記得點贊、收藏,如果有什麼遺漏的,請在評論告訴我,好東西記得分享 ^ _ ^

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章