1. 程式人生 > >類的構造,拷貝構造,析構,賦值(string類)

類的構造,拷貝構造,析構,賦值(string類)

  1. 建構函式初始化表的使用規則:

    1. 如果類存在繼承關係,派生類必須在起初始化表裡呼叫基類的建構函式

    2. 類的const常量只能在初始列表裡被初始化,因為他不能在函式體內用賦值的方式初始化

    3. 類的資料成員的初始化可以採用初始化表或函式體內賦值兩種方式,,這兩種方式的 效率不完全相同

非內部資料型別的成員物件應當採用第一種方式初始化,以獲取更高的效率。例如

class A

{…A(void);// 無引數建構函式

A(const A &other);// 拷貝建構函式

A & operate =( const A&other); // 賦值函式

}

class B

{public:

B(const A &a); // B

的建構函式

private:Am_a;// 成員物件

};

示例 9-2(a)中,類 B 的建構函式在其初始化表裡呼叫了類 A 的拷貝建構函式,從而 將成員物件 m_a 初始化。

示例 9-2 (b)中,類 B 的建構函式在函式體內用賦值的方式將成員物件 m_a 初始化。 我們看到的只是一條賦值語句,但實際上 B 的建構函式幹了兩件事:先暗地裡建立 m_a 物件(呼叫了 A 的無引數建構函式),再呼叫類 A 的賦值函式,將引數 a 賦給m_a

對於內部資料型別的資料成員而言,兩種初始化方式的效率幾乎沒有區別,但後者 的程式版式似乎更清晰些。若類 F 的宣告如下:

class F

{

public:

F(int x, int y);//

建構函式

private:i

nt m_x, m_y;

int m_i, m_j;

};

示例 9-2(c) F 的建構函式採用了第一種初始化方式,示例 9-2(d) F 的構造函 數採用了第二種初始化方式。

2.程式碼展示:

class String 
{
public:   String(const char *str = NULL); // 普通建構函式   
		  String(const String &other); // 拷貝建構函式   
		  ~ String(void);     // 解構函式   
		  String & operator = (const String &other); // 賦值函式   
private:   
	char   *m_data;    // 用於儲存字串 
}; 
// 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;//由於由於 m_data 是內部資料型別,也可以寫成 delete m_data; 
}

// 拷貝建構函式
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);
	cout << "kaobeigouzao" << endl;
}
//賦值函式
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);
	//返回本物件的引用
	cout << "fuzhi" << endl;
	return *this;
	

}
void main()
{
	String a;//構造
	char *str = "fdsgfd";
	String b(a);//拷貝構造
	String c;//構造
	c = a;//賦值
	
}


String 拷貝建構函式與普通建構函式(參見 9.4 節)的區別是:在函式入口處無需與 NULL 進行比較,這是因為引用不可能是 NULL,而指標可以為 NULL。類 String 的賦值函式比建構函式複雜得多,分四步實現:

1)第一步,檢查自賦值。你可能會認為多此一舉,難道有人會愚蠢到寫出 a = a 這樣的自賦值語句!的確不會。但是間接的自賦值仍有可能出現,例如

也許有人會說:“即使出現自賦值,我也可以不理睬,大不了化點時間讓物件複製自己而已,反正不會出錯!

他真的說錯了。看看第二步的 delete,自殺後還能複製自己嗎?所以,如果發現自 賦值,應該馬上終止函式。注意不要將檢查自賦值的 if 語句 if(this == &other) 錯寫成為if( *this == other)

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

3)第三步,分配新的記憶體資源,並複製字串。注意函式 strlen 返回的是有效字串長度,不包含結束符‘\0’。函式 strcpy 則連‘\0’一起復制。

4)第四步,返回本物件的引用,目的是為了實現象 a = b = c 這樣的鏈式表達。注意不要將 return *this 錯寫成 return this 。那麼能否寫成 return other 呢?效果 不是一樣嗎? 不可以!因為我們不知道引數other 的生命期。有可能 other 是個臨時物件,在賦 值結束後它馬上消失,那麼 return other 返回的將是垃圾。

3. 如何在派生類中實現類的基本函式

基類的建構函式、解構函式、賦值函式都不能被派生類繼承。如果類之間存在繼承關係,在編寫上述基本函式時應注意以下事項:

1.派生類的建構函式應在其初始化表裡呼叫基類的建構函式。

   2.基類與派生類的解構函式應該為虛(即加 virtual 關鍵字)。例如

class Base
{
public:
	virtual ~Base()
	{
		cout << "~base" << endl;
	}
};
class Derived :public Base
{
public:
	 virtual~Derived()
	{
		cout << "~Derived" << endl;
	}

};
void main()
{
	Base*Pb = new Derived;
	delete Pb;
}

3.在編寫派生類的賦值函式時,注意不要忘記對基類的資料成員重新賦值。例如:

class Base
{
public:
	Base&operator = (const Base&other);// 類 Base 的賦值函式 
private:
	int m_i, m_j, m_k;

};
class Derived :public Base
{
public:
	Derived &operator=(const Derived&other);
private:
	int m_x, m_y, m_z;
};
Derived&Derived::operator=(const Derived&other)
{
	//(1)檢查自賦值  
	if(this == &other)   return *this;  
	//(2)對基類的資料成員重新賦值  
	Base::operator =(other); // 因為不能直接操作私有資料成員  
	//(3)對派生類的資料成員賦值  
	m_x = other.m_x;  m_y = other.m_y;  m_z = other.m_z;  
	//(4)返回本物件的引用  
	return *this;

}

參考資料:《高質量c++程式設計指南》