《劍指Offer》面試題1:賦值運算子函式
// 面試題1:賦值運算子函式
// 題目:如下為型別CMyString的宣告,請為該型別新增賦值運算子函式。
class CMyString
{
public:
CMyString(char* pData = nullptr);
CMyString(const CMyString& str);
~CMyString(void);
void Print();
private:
char* m_pData;
};
賦值:
int a; //宣告
a = 10; //賦值
運算子:
+ - * / = % < > ?: 等等
賦值運算子:
=
賦值運算子函式:
What?嗯?
其實,這裡涉及到了運算子過載的知識點。我一個遨遊在Java大海里的浪子,突然灌一口C/C++的海水,感覺有點生澀。
什麼是運算子過載?
簡單講,比如說:
+ 加號的本意是 1+1 = 2 在這裡是 累加 的功能
在類似於Java、Python這類高階語言中,當 + 加號 出現在字串之間比如 "Hello" + "World !",那麼這裡的加號就會被用來實現 連線兩個字串 的功能。
加號不幹 累加 的功能,而是實現了 連線兩個字串 的功能,這就叫做 加法運算子的過載。
運算子過載中有一點要求需要特別注意:
過載運算子的引數至少應有一個是類物件(或類物件的引用)
也就是說,引數不能全部是C++的基本型別,以防止使用者修改用於基本型別資料成員的運算子的性質,如下面這樣是不對的:
int operator + (int a,int b)
{
return(a-b);
}
原來運算子+的作用是對兩個數相加,現在企圖通過過載使它的作用改為兩個數相減。
如果允許這樣過載的話,那麼表示式4+3,它的結果是7還是1呢?顯然,這是絕對要禁止的。
Java八大基本資料型別:
byte short int long
float double boolean char
基本資料型別是沒有String的,String的本質是一個使用者類。所以說當加號出現在兩個字串之間的時候,實現連線兩個字串這個功能是加法運算子的過載。
但是,但是,但是,Java語言沒有運算子過載:
Java doesn’t support user-defined operator overloading.
Java不支援使用者自定義操作符過載。
The preferred approach is to define a method on your class to perform the action: a.add(b) instead of a + b.
首選的方法是在類中定義一個方法來實現這個功能,比如說通過 a.add(b) 來實現 a+b 的功能。
The only aspect of Java which comes close to “custom” operator overloading is the handling of + for strings,
Java唯一接近“自定義”運算子過載的方面是 + 用於字串的處理,
which either results in compile-time concatenation of constants or execution-time concatenation using StringBuilder/StringBuffer.
這裡的加法會造成在編譯期或執行期使用StringBuilder/StringBuffer。
那麼回到正題,這道題用的語言是C++,C++是有運算子過載的,給一個類新增賦值運算子函式。這個類就是一個字串類,名為:CMyString
賦值運算子函式,就是過載賦值運算子了。下面是該函式的宣告:
CMyString& operator = (const CMyString& str);
CMyString& 是函式的型別 operator = 整體看成函式名 const CMyString&是引數的型別 str 是引數的引用名
我們要做的就是書寫函式體。
初級程式設計師版:
1 CMyString& operator = (const CMyString& str){
2 if(this == &str){
3 return *this;
4 }else{
5 delete []m_pData;
6 m_pData = NULL;
7 m_pData = new char[strlen(str.m_pData)+];
8 strcpy(m_pData, str.m_pData);
9 return *this;
10 }
11 }
這一版本的Bug之處就在於,假如第5、6行程式碼執行完成之後,第7行程式碼在分配記憶體的時候,分配失敗了,那麼我原先的資料也就沒有了。
這樣原先的物件就會成為一個空指標,想象一下,一個空指標在程式中,一旦程式後文又用到這個物件,那麼這個程式極有可能崩潰!!!
考慮到異常安全性,下面的程式碼是
高階程式設計師版:
1 CMyString& operator = (const CMyString& str){
2 if(this == &str){
3 return *this;
4 }else{
5 CMyString strTemp(str);
6 char* pTemp = strTemp.m_pData;
7 strTemp.m_pData = m_pData;
8 m_pData = pTemp;
9 return *this;
10 }
11 }
不理解的地方:
CMyString strTemp(str); 看不懂這句程式碼,這句程式碼怎麼就建立一個臨時物件了,
這難道不是 型別名 函式名 引數?如果是這樣的話,那麼這個函式連宣告都沒有直接就用了?
既然臨時物件一但執行結束就會被析構,那麼為什麼要進行交換?直接把臨時物件的值賦值給當前物件的值不就行了?
一行程式碼
m_pData = strTemp.m_pData
不比三行程式碼強嗎?
如果是說不能這樣直接進行賦值,需要有個 char* pTemp的中間變數,那也應該是
兩行程式碼:
char* pTemp = strTemp.m_pData;
// strTemp.m_pData = m_pData;
m_pData = pTemp;
中間那行程式碼的意義何在,難不成是寫交換演算法寫順手了???
百思不得其姐......(所以我現在還不是高階程式設計師......)
總結:
1、 C++中通過運算子函式(關鍵字operator)可以對運算子進行過載;
2、如果要為一個類寫賦值運算子函式,需要考慮記憶體分配失敗的異常情況下要原物件不能為空指標的情況;
3、 一個物件的值,可以在建立的時候通過建構函式進行賦值,
也可以把另一個物件的值通過賦值運算子賦值給當前物件,
也可以通過賦值運算子進行多個物件的連續賦值,
這,就是賦值運算子函式的意義;
4、C++很飄逸,很成功,很失敗,臨時物件很難懂;
參考文章:(每篇文章我都會去點贊,好的文章就應該公諸於世,大家共同學習)
部落格園:
RUNOOB:
CSDN: