C++基於對象--Class with pointer--string類的簡單實現

本文所談的對象是帶有指針的類(Class with pointer);這時不能使用編譯器自動合成的三大函數(Big three):拷貝構造、拷貝賦值和析構需要自己去顯式的定義着三大函數;

primer中給出的建議是:如果一個類需要需要自定義析構函數,幾乎可以肯定它也需要自定義拷貝賦值運算符和拷貝構造函數。

所以一般情況下,這三個函數總是一起自定義,或者一起不定義

在實際中,如果定義了其他構造函數,那麼最好也提供一個默認構造函數。

 

1、拷貝構造函數

定義:如果一個構造函數的第一個參數是自身類類型的引用(必須是引用),且任何額外參數都有默認值,則此構造函數是拷貝構造函數。

並且參數幾乎總是const的引用

對於不含指針的類,合成拷貝構造函數和自定義的拷貝構造函數等價;

 

拷貝初始化不僅發生在用=定義變量時,下列情況也會發生:

(1)將一個對象作爲實參傳遞給一個非引用類型的形參。

(2)從一個返回類型爲非引用類型的函數返回一個對象。

(3)用花括號列表初始化一個數組中的元素或聚合類中的成員。

 

對於編譯器自動生成的合成拷貝構造函數,依次拷貝每個成員變量--copies each member varible(nembers,objects,arrays)。

一旦成員裏面有指針,也會拷貝指針,造成的結果就是兩個指針指向同一片內存。最終導致類的析構失敗。

 

2、拷貝賦值運算符

拷貝賦值其實就是對賦值運算符重載。

對於賦值運算符,必須定義爲成員函數。

賦值運算符通常返回一個指向類對象的引用。

 

3、析構函數

析構函數釋放對象使用的資源,並銷燬對象的非static數據成員。

隱式銷燬一個內置指針類型的成員不會delete它所指向的對象。

 

下面的程序來自於--侯捷老師的網絡課程《C++面向對象高級開發》

通過程序詳細的介紹Class with pointer的設計。

 

string.h

#ifndef __MYSTRING__
#define __MYSTRING__
#define _CRT_SECURE_NO_WARNINGS
class String
{
public:
	//默認構造函數,並設置默認值
	String(const char* cstr = 0);

	//Big Three三大函數:拷貝構造,拷貝賦值,析構。編譯器會自動給出三大函數。
	//對於class with pointer,三大函數需要自己寫,不能使用編譯器給的默認版本。
	//不然會出現內存泄漏(memory leak)和別名(alias):多個指針指向同一塊內存。

	//1. 拷貝構造
	String(const String& str);
	//2. 拷貝賦值,返回值爲String&,可以滿足連串賦值的操作
	String& operator=(const String& str);
	//3. 析構
	~String();

	char* get_c_str() const { return m_data; }
private:
	char* m_data;
};

#include <cstring>

inline
String::String(const char* cstr)
{
	//對傳入的指針進行判斷
	if (cstr) {
		m_data = new char[strlen(cstr) + 1];
		strcpy(m_data, cstr);
	}
	//若未指定初值
	else { 
		m_data = new char[1]; //爲了搭配上面的寫法
		*m_data = '\0';
	}
}

inline
String::~String()
{
	delete[] m_data;
}

inline
String& String::operator=(const String& str)
{
	//檢查自我賦值(self assignment)
	//1. 提高效率
	//2. 正確性,如果自我賦值會出錯
	if (this == &str)
		return *this;

	//經典寫法1-2-3
	//1. 先清空自己指向的內存
	delete[] m_data;  
	//2. 分配需要的內存
	m_data = new char[strlen(str.m_data) + 1];
	//3. 最後拷貝數據
	strcpy(m_data, str.m_data);
	return *this;
}

//深拷貝,編譯器自動給出的爲淺拷貝
inline
String::String(const String& str)
{
	m_data = new char[strlen(str.m_data) + 1];
	strcpy(m_data, str.m_data);
}

#include <iostream>
using namespace std;

// <<的重載必須寫爲非成員函數
// 如果寫成成員函數,使用者則需要這樣寫:string << cout; 這樣不符合常用習慣。
ostream& operator<<(ostream& os, const String& str)
{
	os << str.get_c_str();
	return os;
}

#endif


string-test.cpp

#include "string.h"

int main()
{
	String str1;
	String str2("hello str2");//默認構造
	String str3 = "hello str3";//默認構造

	String str4(str2);//拷貝構造
	String str5 = str4;//拷貝構造,同上

	str1 = str2;//賦值構造

	//重載<<
	cout << "str1:" << str1 << endl;
	cout << "str2:" << str2 << endl;
	cout << "str3:" << str3 << endl;
	cout << "str4:" << str4 << endl;
	cout << "str5:" << str5 << endl;

	//new:先分配memory,再調用ctor。
	String* pstr = new String[3];
	//編譯器將上面語句翻譯爲下面三個動作
	//1. void* mem = operator new(sizeof(String)*3); 其內部調用malloc(n);
	//2. pc = static_cast<String*>(mem);
	//3. pc->String::String();調用三次構造

	pstr[0] = str1;
	pstr[1] = str2;
	pstr[2] = str3;
	cout << "pstr[1]:" << pstr[0] << endl;
	cout << "pstr[2]:" << pstr[1] << endl;
	cout << "pstr[3]:" << pstr[2] << endl;

	//delete:先調用dtor,再釋放memory;
	delete[] pstr;
	//編譯器將上面語句翻譯爲下面兩個動作
	//1. String::~String(pstr);調用三次析構。
	//2. operator delete(pstr);  其內部調用free(pstr);
}

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章