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;
}
剛剛上面說,如果不主動編寫拷貝建構函式和賦值函式,編譯器將以“位拷貝
對於編譯器,如果不主動編寫拷貝函式和賦值函式,它會以“位拷貝”的方式自動生成預設的函式。如果重寫賦值函式和拷貝建構函式後,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程式設計指南