題目:如下類型CMyString的聲明,請爲該類添加賦值運算符函數
class CMyString
{
public:
CMyString(char* pdata = NULL);
CMyString(const CMyString& str);
//賦值函數
CMyString& operator=(const CMyString& str);
~CMyString();
private:
char* m_data;
};
面試官考察的點:
1、 是否把返回值的類型聲明爲該類型的引用,並在函數返回前,返回自身的引用(*this)。只有返回一個引用,纔可以允許連續賦值。如果函數的返回值爲void,則像str1=str2=str3這樣的連續賦值將不會被通過
2、 是否把傳入的參數類型聲明爲常引用。如果傳入的參數不是引用而是實=實例的話,那麼從形參到實參要進行一次拷貝構造函數的調用,把參數聲明爲引用,可以避免無謂的消耗,提高效率。同時,在賦值的時候,不應該改變傳入參數的狀態,所以,應該聲明爲常引用,加上const關鍵字
3、 是否釋放自身的空間,否則程序將出現內存泄漏
4、 避免自賦值。判斷傳入的參數是否和當前的實例*this是一個實例,如果是同一個實例,則不進行賦值操作,直接返回。如果事先不進行判斷,在釋放自身實例的內存的時候就會出現問題,一旦釋放了自身的內存,傳入的參數的內存也同時釋放了,因此再也找不到需要賦值的內容了
//初級程序員解法:
CMyString& CMyString::operator=(const CMyString& str)
{
//防止自賦值
if (&str == this)
return *this;
//釋放自身空間
delete[] m_data;
m_data = NULL;
//分配空間,賦值
m_data = new char[strlen(str.m_data)+1];
strcpy(m_data, str.m_data);
//返回自身引用
return *this;
}
//考慮異常安全的解法,高級程序員必備
在前面的函數中,我們在分配內存之前先delete釋放了自身的內存。但是如果此時內存不足,分配內存的時候拋出異常,那麼m_data將是一個空指針,很容易導致程序崩潰,違背了異常安全原則
要想在賦值函數中實現異常安全性。有兩種方法:1、一個簡單的方法是先用new分配新內容,再delete釋放已有的內容,這樣在分配成功之後再釋放原來的內容,也就是當分配失敗時可以保證CMyString的實例不會被修改。2、還有一個更好的方法就是創建一個臨時對象,再交換臨時對象和原來對象
CMyString& CMyString::operator=(const CMyString& str)
{
if (&str != this)
{
//先通過拷貝構造函數創建一個臨時對象
CMyString strtmp(str);
//交換臨時對象和自身的m_data
char* pTmp = m_data;
m_data = strtmp.m_data;
strtmp.m_data = pTmp;
}
return *this;
}
這個函數中,先創建一個臨時對象,然後交換臨時對象和實例自身的m_data,由於臨時對象是一個局部對象,出了if語句就會自動調用臨時對象的析構函數,就會把臨時對象所指的那塊內存釋放掉。
在這個新的代碼中,我們在CMyString的構造函數裏用new分配內存,如果內存不足拋出異常,這時,我們還沒有修改原來實例的狀態。就保證了異常安全性