1. 程式人生 > >C++:拷貝建構函式&賦值運算子的過載函式

C++:拷貝建構函式&賦值運算子的過載函式

拷貝建構函式
         用一個已經存在的物件來生成一個相同型別的新物件。(淺拷貝)
預設的拷貝建構函式
         如果自定義了拷貝建構函式,編譯器就不在生成預設的拷貝建構函式。 
         如果沒有自定義拷貝建構函式,但在程式碼中用到了拷貝建構函式,編譯器會生成預設的拷貝建構函式。
深拷貝&淺拷貝:
         系統預設的拷貝建構函式是淺拷貝,類中含有指標型別的變數,須自定義拷貝建構函式用深拷貝來實現。
         淺拷貝只是對指標的拷貝,拷貝後兩個指標指向同一個記憶體空間,所指向的空間內容並沒有複製,而是由兩個物件共用。深拷貝不但對指標進行拷貝,而且對指標指向的內容進行拷貝,經深拷貝後的指標是指向兩個不同地址的指標。

如圖:   
             

思考:
      
當物件中存在指標成員時,為什麼需要自己實現拷貝建構函式?如果不,會出現怎樣的問題?

看程式碼

#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所指的資料同時被銷燬。