1. 程式人生 > >C++的拷貝建構函式、operator=運算子過載,深拷貝和淺拷貝、explicit關鍵字

C++的拷貝建構函式、operator=運算子過載,深拷貝和淺拷貝、explicit關鍵字

1、在C++編碼過程中,類的建立十分頻繁。

簡單的功能,當然不用考慮太多,但是從進一步深刻理解C++的內涵,類的結構和用法,編寫更好的程式碼的角度去考慮,我們就需要用到標題所提到的這些內容。

最近,在看單例模式,覺得十分有趣,然而如果想要掌握單例模式,就必須掌握這些內容。下面是我的一些學習總結,參考了很多部落格內容。文末將註明出處。

2、先上程式碼

// testSingleMode.cpp : 定義控制檯應用程式的入口點。
//
#include "stdafx.h"
#include <iostream>

using namespace std;

class Complex
{
private:
	double m_real;
	double m_imag;

public:
	Complex(void){
		m_real = 0.0;
		m_imag = 0.0;
	}

	Complex(double real, double imag){
		m_real = real;
		m_imag = imag;
	}

	Complex(const Complex & c){	//這裡就是最經典的拷貝構造函數了
		m_real = c.m_real;
		m_imag = c.m_imag;
	}

	Complex &operator = (const Complex &rhs){	//這裡就是最經典的operator=操作符過載了
		if (this == &rhs){
			return *this;
		}

		this->m_real = rhs.m_real;
		this->m_imag = rhs.m_imag;

		return *this;
	}

	explicit Complex::Complex(double r){	//explicit的用法,只適用於1個引數的情況
		m_real = r;
		m_imag = 0.0;
	}

};


int main()
{
	Complex c1, c2;	//呼叫 第15行 預設無引數的建構函式
	Complex c3(1.0, 2.5);	//呼叫 第20行 具有2個形參的建構函式
	//Complex c3 = Complex(1.0, 2.5);	//和上一行是一個意思,所以這個註釋了
	c1 = c3;	//呼叫 第30行 過載operator=運算子
	c2 = c3;	//呼叫 第30行 過載operator=運算子
	//c2 = 5.2;	//隱式轉換,需要去掉41行的explicit關鍵字,才可編譯通過

	Complex c5(c2);		//呼叫 第25行 拷貝建構函式
	Complex c4 = c2;	//呼叫 第25行 拷貝建構函式

	getchar();
	return 0;
}

【注1】explicit 只適用於建構函式只含有1個引數的情況,加上這個關鍵字,意味著不支援建構函式隱式轉換,可以避免一些誤解。如果去掉這個關鍵字,那麼程式碼裡面的:c2 = 5.2 ; 就是可以執行的了。

【注2】為什麼函式中可以直接訪問物件c的私有成員?
答:(網上)因為拷貝建構函式是放在本身這個類裡的,而類中的函式可以訪問這個類的物件的所有成員,當然包括私有成員了。

3、上面程式碼的註釋,已經是非常的清楚了

自定義拷貝建構函式是一種良好的程式設計風格,

它可以阻止編譯器形成預設的拷貝建構函式,防止出錯。

淺拷貝:如果自己不寫拷貝建構函式,系統會預設生成一個,而系統的拷貝建構函式是淺拷貝。

深拷貝:自己寫一個拷貝建構函式,系統就不會產生了預設的構造函數了(來自網上說法)。自己寫的這個拷貝建構函式,當然會有開闢空間的動作,所以是深拷貝。也就是說,如果生成類的例項的時候,呼叫了自己寫的拷貝建構函式,那麼在記憶體空間上,必然是會開闢新的空間,而不用擔心只是一個指標。很多時候,我們希望得到的類的例項是各自獨立的,各有各的空間。如果希望得到指標,那就不用操心這麼多。

在某些狀況下,類內成員變數需要動態開闢堆記憶體,如果實行淺拷貝,也就是把物件裡的值完全複製給另一個物件,如A=B。這時,如果B中有一個成員變數指標已經申請了記憶體,那A中的那個成員變數也指向同一塊記憶體。這就出現了問題:當B把記憶體釋放了(如:析構),這時A內的指標就是野指標了,出現執行錯誤。

4、總結

(1)深拷貝:如果一個類擁有資源,當這個類的物件發生複製過程的時候,資源重新分配,這個過程就是深拷貝。反之,沒有重新分配資源,就是淺拷貝。

(2)什麼時候用到拷貝建構函式

  a.一個物件以值傳遞的方式傳入函式體; 
  b.一個物件以值傳遞的方式從函式返回;
  c.一個物件需要通過另外一個物件進行初始化。 (3)深拷貝好還是淺拷貝好? 如果實行錢拷貝,也就是把物件裡的值完全複製給另一個物件,如A=B。這時,如果B中有一個成員變數指標已經申請了記憶體,那A中的那個成員變數也指向同一塊記憶體。這就出現了問題:當B把記憶體釋放了(如:析構),這時A內的指標就是野指標了,出現執行錯誤。

(4)程式碼例子2

#include "stdafx.h"
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
using namespace std;

class Person
{
public:

	// 建構函式
	Person(char * pN)
	{
		cout << "一般建構函式被呼叫 !\n";
		m_pName = new char[strlen(pN) + 1];
		//在堆中開闢一個記憶體塊存放pN所指的字串
		if (m_pName != NULL)
		{
			//如果m_pName不是空指標,則把形參指標pN所指的字串複製給它
			strcpy_s(m_pName, strlen(pN) + 1, pN);
		}
	}

	// 下面自己設計複製建構函式,實現“深拷貝”,即不讓指標指向同一地址,而是重新申請一塊記憶體給新的物件的指標資料成員
	Person(Person & chs)
	{
		cout << "拷貝建構函式被呼叫 !\n";
		// 用運算子new為新物件的指標資料成員分配空間
		m_pName = new char[strlen(chs.m_pName) + 1];

		if (m_pName)
		{
			// 複製內容
			strcpy_s(m_pName, strlen(chs.m_pName) + 1, chs.m_pName);
		}

		// 則新建立的物件的m_pName與原物件chs的m_pName不再指向同一地址了
	}

	//// 系統建立的預設複製建構函式,只做位模式拷貝
	//Person(Person & p)
	//{
	//	//使兩個字串指標指向同一地址位置         
	//	m_pName = p.m_pName;
	//}

	~Person()
	{
		delete m_pName;
	}

	void getName(){
		cout << m_pName << endl;
	}

private:
	char * m_pName;
};

void main()
{
	Person man("lujun");
	man.getName();
	Person woman(man);
	woman.getName();

	getchar();
}

程式執行環境: VS2013 

輸出結果:


==============

參考文獻:

http://blog.csdn.net/lpp0900320123/article/details/39007047

http://www.cnblogs.com/xkfz007/archive/2012/05/11/2496447.html

http://www.cnblogs.com/raichen/p/4752025.html

http://blog.163.com/haixing_03031102/blog/static/120105509200972855328532/

http://blog.csdn.net/waitforfree/article/details/10137495

https://zhidao.baidu.com/question/1638161405180160020.html