1. 程式人生 > >C++ String類的建構函式、拷貝建構函式的實現

C++ String類的建構函式、拷貝建構函式的實現

建構函式、解構函式與賦值函式是每個類最基本的函式,在一些公司的面試中也會經常問到這方面的問題。每個類只有一個解構函式和一個賦值函式,但可以有多個建構函式(包含一個拷貝建構函式,其它的稱為普通建構函式)。對於任意一個類A,如果不手動編寫上述函式,C++編譯器將自動為類A生成四個預設的函式

   A(void);                    // 預設的無引數建構函式

   A(const A &a);                // 預設的拷貝建構函式

   ~A(void);                    // 預設的解構函式

    A& operate =(const A &a);    // 預設的賦值函式

雖然有自動生成,但是還是有必要手動寫上述函式的。因為:

(1)如果使用“預設的無引數建構函式”和“預設的解構函式”,等於放棄了自主“初始化”和“清除”的機會,C++發明人Stroustrup的好心好意白費了。

(2)“預設的拷貝建構函式”和“預設的賦值函式”均採用“位拷貝”而非“值拷貝”的方式來實現,倘若類中含有指標變數,這兩個函式註定將出錯。

下面以類String的設計與實現為例,深入探討這個道理。String的結構如下:

class String{
private:
	char *m_data;//成員變數,用於儲存字串

public:
	String(const char *str=NULL);//普通建構函式
	String(const String &other);//拷貝建構函式
	~String();//解構函式
	String &operator=(const String &other);//賦值函式
};

String類的普通建構函式和解構函式實現如下:

//String的普通建構函式
String::String(const char *str)
{
	if (str==NULL)
	{
		m_data=new char[1];
		*m_data='\0';
	}
	else
	{
		int length=strlen(str);
		m_data=new char[length+1];
		strcpy(m_data,str);
	}
}
//String類的解構函式
String::~String(void)
{
	delete [] m_data;
}

剛剛上面說,如果不主動編寫拷貝建構函式和賦值函式,編譯器將以“位拷貝

”的方式自動生成預設的函式。倘若類中含有指標變數,那麼這兩個預設的函式就隱含了錯誤。以類String的兩個物件a,b為例,假設a.m_data的內容為“hello”,b.m_data的內容為“world”。位拷貝拷貝的是地址,而值拷貝則拷貝的是內容。現將a賦給b,預設賦值函式的“位拷貝”意味著執行b.m_data = a.m_data,雖然b.m_data所指向的內容會變成”hello”,但是這將造成三個錯誤:一是b.m_data原有的記憶體沒被釋放,造成記憶體洩露;二是b.m_data和a.m_data指向同一塊記憶體,a或b任何一方變動都會影響另一方;三是在物件被析構時,m_data被釋放了兩次。

對於編譯器,如果不主動編寫拷貝函式和賦值函式,它會以“位拷貝”的方式自動生成預設的函式。如果重寫賦值函式和拷貝建構函式後,b.m_data=a.m_data,進行的是值拷貝,會將a.m_data的內容賦給b.m_data,b.m_data還是指向原來的記憶體區域,但是其內容改變。

有下面4個語句:

String a(“hello”);

String b(“world”);

String c = a;    // 呼叫了拷貝建構函式,最好寫成 c(a);

c = b; // 呼叫了賦值函式

第3語句的風格較差,宜改寫成String c(a) 以區別於第4語句

下面是類String的拷貝建構函式與賦值函式

// 拷貝建構函式
String::String(const String &other)
{
	// 允許操作other的私有成員m_data
	int length=strlen(other.m_data);
	m_data=new char[length+1];
	strcpy(m_data,other.m_data);
}

//賦值函式
String & String::operator = (const String &other)
{
	//檢查自賦值
	if (this==&other)
	{
		return *this;
	}
	//釋放原有的記憶體資源
	delete []m_data;
	//分配新的記憶體資源,並複製內容
	int length=strlen(other.m_data);
	m_data=new char[length+1];
	strcpy(m_data,other.m_data);
	//返回本物件的引用
	return *this;
}

類String拷貝建構函式與普通建構函式(參見9.4節)的區別是:在函式入口處無需與NULL進行比較,這是因為“引用”不可能是NULL,而“指標”可以為NULL。

類String的賦值函式比建構函式複雜得多,分四步實現:

(1)      第一步,檢查自賦值。

(2)      第二步,用delete釋放原有的記憶體資源。如果現在不釋放,以後就沒機會了,將造成記憶體洩露。

(3)      第三步,分配新的記憶體資源,並複製字串。

(4)      第四步,返回本物件的引用,目的是為了實現象 a = b = c 這樣的鏈式表達。注意不要將 return *this 錯寫成 return this.

Ref

高質量C++/C程式設計指南