《劍指Offer——名企面試官精講典型編程題》
作者:何海濤
一、書上原題再現
面試題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 }
四、調試步驟
使用DevC++進行調試
五、總結
不理解的地方:
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++很飄逸,很成功,很失敗,臨時對象很難懂;
5、這道題的精髓我認為是就兩點:
一是如何把這個operator = 函數完整的寫出來,
二是在寫的過程需要考慮內存泄漏的情況。
六、聲明與致謝
本題源代碼請移步《劍指Offer》作者何海濤的GitHub:https://github.com/zhedahht/CodingInterviewChinese2/blob/master/01_AssignmentOperator/AssignmentOperator.cpp
參考文章:(每篇文章我都會去點贊,好的文章就應該公諸於世,大家共同學習)
博客園:
顏之年《C++重載運算符的規則詳解》:https://www.cnblogs.com/summernight/p/8541079.html
同勉共進《一文說盡C++賦值運算符重載函數(operator=)》:https://www.cnblogs.com/zpcdbky/p/5027481.html#top
默默淡然《運算符重載詳解》:https://www.cnblogs.com/liangxiaofeng/p/4311796.html
RUNOOB:
Java基本數據類型:http://www.runoob.com/java/java-basic-datatypes.html
CSDN:
michellechouu《【C++】賦值運算符函數》:https://blog.csdn.net/michellechouu/article/details/47298445
liaotl10《才知道java竟然沒有運算符重載》:https://blog.csdn.net/liaotl10/article/details/74999757
Do丶YouMissing《java中是否對“+”,“=”,“+=”重載》:https://blog.csdn.net/caonima0001112/article/details/50492718
viclee108《淺析Python運算符重載》:https://blog.csdn.net/goodlixueyong/article/details/52589979
技術小黑屋:https://droidyue.com/