C/C++面試題(3)——劍指offer1(賦值運算子函式)
阿新 • • 發佈:2019-01-01
今天又複習了C++面試題,這道題的目的是:給出一個類的宣告,然後寫出這個類的建構函式、解構函式、拷貝建構函式和運算子過載函式。
題目
//題目:如下為型別CMyString的宣告,請為該型別新增賦值運算子函式。
class CMyString
{
public:
CMyString(char *pData=NULL);//建構函式
CMyString();//解構函式
private:
char* m_pData;//資料域,字元指標
};
接下來分析如何一步步從最一般的解法到考慮異常安全性的解法:
那麼什麼是賦值運算子函式呢?
賦值運算子函式,由operator後面跟所定義的操作符符號,通過定義名為operator=函式來對賦值進行定義。該操作符函式有兩個形參:第一個形參對應左邊的運算元(隱式繫結到this指標了),第二個形參對應右運算元。返回型別應該與內建賦值運算返回的型別相同,內建型別的賦值運算返回對右運算元的引用,賦值操作符也返回對同一型別的引用。例如: class B { public: B& operator=(const B &);//賦值運算子函式的宣告 };
編寫賦值運算子函式應該注意的地方:
對於定義一個賦值運算子函式時,需要注意一下幾點:
(1)函式的返回型別必須是一個引用,因為只有返回自身的引用(也即 *this),才可以連續賦值;
(2)傳入的引數型別宣告為常量引用。如果傳入的引數不是引用而是例項,那麼從形參到實參會呼叫一次複製建構函式。把引數宣告為常量引用,可以提高程式碼效率,同時賦值運算函式內不會改變傳入的例項狀態;
(3)一定要記得釋放例項自身已有的記憶體,否則程式容易出現記憶體洩露
(4)注意傳入的引數和當前的例項(*this)是不是同一個例項,如果是同一個,則不用進行賦值操作,直接返回即可。
那麼賦值運算子函式的宣告是什麼呢?
CMyString& operator=(const CMyString& str);//過載運算子
經典的解法如下:
#include<iostream> #include<stdlib.h> using namespace std; class CMyString { public: CMyString(char *pData=NULL);<span style="white-space:pre"> </span>//建構函式 CMyString(const CMyString& str);<span style="white-space:pre"> </span>//拷貝建構函式 ~CMyString();<span style="white-space:pre"> </span>//解構函式 CMyString& operator=(const CMyString& str);//過載運算子 void Print();<span style="white-space:pre"> </span>//列印字串 private: char* m_pData;<span style="white-space:pre"> </span>//資料域,字元指標 }; void CMyString::Print() { cout<<m_pData<<endl; } //建構函式 CMyString::CMyString(char *pData) { if(pData==NULL)<span style="white-space:pre"> </span>//如果建構函式的引數為空 { m_pData=new char[1]; m_pData[0]='\0';<span style="white-space:pre"> </span>//初始化字串,內容為'\0' } else<span style="white-space:pre"> </span>//如果建構函式的引數不為NULL,那麼首先求出字串長度,然後new一個長度為len+1的字元陣列 { int len=strlen(pData); m_pData=new char[len+1]; strcpy(m_pData,pData);//字串拷貝 } } //解構函式 CMyString::~CMyString() { delete[] m_pData; } //拷貝建構函式,拷貝建構函式與建構函式的思路非常類似。 CMyString::CMyString(const CMyString& str) { int len=strlen(str.m_pData); m_pData=new char[len+1]; strcpy(m_pData,str.m_pData); } //過載運算子 CMyString& CMyString::operator=(const CMyString& str) { //如果傳入的引數與當前的例項是同一個例項,則直接返回自身 if(this==&str) return *this; //釋放例項自身已有記憶體 delete[] m_pData; m_pData=NULL; //在刪除自身記憶體以後在重新new一個長度為len+1的字元陣列,類似拷貝建構函式 int len=strlen(str.m_pData); m_pData=new char[len+1]; strcpy(m_pData,str.m_pData); } void main() { char* strs="hello xuxing"; CMyString str1(strs); CMyString str2; str2=str1; str1.Print(); str2.Print(); system("pause"); }</span>
前面程式碼的不足之處: 前面函式中,我們在分配記憶體之前就先呼叫了delete [] m_pData; 釋放了m_pData的記憶體。但是現在問題出現了,加入我們此時釋放了記憶體,但是剩下的記憶體又不夠我們 new char的空間,這個時候m_pData 將會是一個空指標,這樣非常導致程式崩潰。那我們如何解決這個問題呢?由於我們擔心先釋放會發生記憶體不夠的情況,那麼我們就先非配記憶體,並且判斷記憶體是否非配成功,在分配成功之後,再行釋放記憶體,這樣就做到萬無一失了。
改進之後的程式碼:
<span style="font-family:Microsoft YaHei;font-size:14px;">//過載運算子
CMyString& CMyString::operator=(const CMyString& str)
{
if(this!=&str)//
{
CMyString strTemp(str);<span style="white-space:pre"> </span>//使用建構函式建立一個臨時物件
//交換臨時物件與當前物件的m_pData值
char* pTemp=strTemp.m_pData;
strTemp.m_pData=m_pData;
m_pData=pTemp;
}
return *this;
}</span>
改進之後的優點分析: 這樣的一個好處是在執行完if語句以後,因為除了strTemp的作用於,該例項會自動呼叫解構函式,把strTemp.m_pData所指向的記憶體釋放掉,而此時strTemp.m_pData指向的是例項原先m_pData指向的記憶體,並沒有釋放當前指向的pTemp這一塊記憶體。還有一點是通過建構函式為臨時例項分配記憶體,如果在new char過程中丟擲異常,並沒有改變該例項m_pData所指向的內容,也沒有釋放記憶體,所以是異常安全性的。