c++ 賦值運算符重載和拷貝構造函數區別以及不寫時可能引發的問題

一、賦值運算符重載和拷貝構造函數區別

#include <iostream>
using namespace std;

class Element
{
private:
	int *m_pdata;
public:

	Element(int data):m_pdata(NULL) // 指針的成員函數一定在初始化列表中賦值爲初始化爲NULL
	{
		cout << "構造函數" << endl;
		m_pdata = new int(data);
	}
	Element(const Element& e)
	{
		cout << "拷貝構造" << endl;
	}
	void operator=(const Element& e) // 返回值是void,有啥問題呢?
	{
		cout << "運算符重載" << endl;
	}
	~Element()
	{
		delete m_pdata;
	}
};


int main()
{
	Element e1(1);
	Element e2(2);
	Element e3 = e1;
	e2 = e1;
	system("pause");
	return 0;
}

運行上面的程序後,輸出如下:
在這裏插入圖片描述
所以最後可以得出結論:
(1)拷貝構造函數是在初始化並賦值的時候調用的。比如說例子中的Element e3 = e1,或者stl容器在添加元素的時候都會調用拷貝構造函數
(2)賦值運算符重載只會在單純的賦值操作中調用,被賦值的對象已經創建過了。

二、可能引發的問題

如果沒寫自定義的拷貝構造函數或者賦值運算符重載的話,調用時會默認使用淺拷貝,當成員函數中有指針的話會出大問題
上面的成員變量就是指針,把上面的代碼稍微修改一下,去掉自定義的拷貝構造函數或者賦值運算符重載。

#include <iostream>
using namespace std;

class Element
{
private:
	int *m_pdata;
public:

	Element(int data):m_pdata(NULL) // 指針的成員函數一定在初始化列表中賦值爲初始化爲NULL
	{
		cout << "構造函數" << endl;
		m_pdata = new int(data);
	}
	~Element()
	{
		cout << "被釋放了" << "pdata:" << m_pdata << endl;
		// 開始認爲加上判空以及delete後賦值爲空可以避免崩潰,但是想多了,還是對指針理解不到位啊.
		// 給一個對象的成員變量修改後不會修改其他對象中成員變量的引用。
		if (m_pdata) // 
		{
			delete m_pdata;
			m_pdata = NULL;
		}
		
	}
};

// 放到函數中,變量生存空間變成了函數內部,結束後可以直接調用析構函數
void  test()
{
	Element e1(1);
	Element e2(2);
	Element e3 = e1;
	e2 = e1;
}

int main()
{
	test();
	system("pause");
	return 0;
}

上面的程序在運行結果:
在這裏插入圖片描述
在第二次釋放的時候就崩潰了。因爲三個對象e1,e2,e3中成員變量m_pdata的值都爲00035450,第一個對象做析構的時候,把00035450這個地址的內存區域delete掉了,其他兩個對象的m_pdata卻還指向被釋放的內存區域00035450,也就變成了野指針,然後其他兩個對象調用析構的時候對野指針進行delete就崩潰了
所以說,,當成員變量有指針的時候,還是自己寫拷貝構造和賦值運算符重載來進行深拷貝吧,不然出問題的機率還是很大的。

BTW:我在賦值運算符重載的時候,返回的值是void(這樣是可以的,因爲賦值運算符本質是對this的修改,只要在重載函數中改了this就可以了,返回值是什麼都可以),但是這樣的話就不能進行a=b=c這樣的連續賦值操作了,所以還是建議返回值爲該類型的引用(用引用減少一次臨時的拷貝構造

修改後的代碼:

#include <iostream>
using namespace std;

class Element
{
private:
	int *m_pdata;
public:

	Element(int data):m_pdata(NULL) // 指針的成員函數一定在初始化列表中賦值爲初始化爲NULL
	{
		cout << "構造函數" << endl;
		m_pdata = new int(data);
	}
	Element(const Element& e)
	{
		cout << "拷貝構造" << endl;
		m_pdata = new int(*(e.m_pdata));
	}
	Element& operator=(const Element& e) // 返回值是void,有啥問題呢?
	{
		cout << "運算符重載" << endl;
		m_pdata = new int(*(e.m_pdata));
		return *this;
	}
	~Element()
	{
		cout << "被釋放了" << "pdata:" << m_pdata << endl;
		// 開始認爲加上判空以及delete後賦值爲空可以避免崩潰,但是想多了,還是對指針理解不到位啊.
		// 給一個對象的成員變量修改後不會修改其他對象中成員變量的引用。
		if (m_pdata) // 
		{
			delete m_pdata;
			m_pdata = NULL;
		}
		
	}
};

// 放到函數中,變量生存空間變成了函數內部,結束後可以直接調用析構函數
void  test()
{
	Element e1(1);
	Element e2(2);
	Element e3 = e1;
	e2 = e1;
}

int main()
{
	test();
	system("pause");
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章