一.背景
在複習《C++基礎與提高》時,自己實現運算子過載(i++)時,幾次都報錯。其實還是自己對運算子過載這一部分內容理解得不夠透徹,於是再次看了下書上的內容,理解算是加深了一些,於是提筆記錄一下。
環境:win10,QT4.8
二.概述
這部分內容主要關於在過載函式中,函式前要不要加const,何時加const,返回型別要不要加&(引用)修飾,何時加&(引用)的問題,還有臨時物件的問題。關於為什麼要過載,過載的規則,友元過載、成員過載的區別之類的知識點,這裡就不贅述了。
三.內容
以類Complex為例
1 class Complex 2 { 3 public: 4 Complex(double x = 0, double y = 0) 5 :m_x(x), m_y(y){} 6 7 void dis() 8 { 9 cout<<"("<<m_x<<", "<<m_y<<")"<<endl; 10 } 11 protected: 12 double m_x; 13 double m_y; 14 };
1.以實現單目運算子prefix++和surfix++為例。
先提這個例子,一是因為我在複習這塊時遇到了一點問題,二是這個有點特別,涉及到啞元的問題。
prefixe++
1).考慮基本資料型別,以int型別為例,如下的操作都是可以的;
1 int a = 1; 2 ++a; 3 ++++a;
2).先實現基本的語義,程式碼如下:
1 Complex Complex::operator++(void) 2 { 3 m_x++; 4 m_y++; 5 return *this; 6 }
3)考慮新增
過載函式返回的是物件自身,並且需要修改物件,我們即可以想到返回的是引用型別。注意,此時引用指向的物件在過載函式呼叫時就已經存在了。
4)先執行一下,看下是否能編譯通過
++c1;
++++c1;
此時過載函式實現的效果,與基本型別效果一致,符合預期,此時就不考慮過載函式前面是否加const修飾了。
1 #include <iostream> 2 3 using namespace std; 4 5 class Complex 6 { 7 public: 8 Complex(double x = 0, double y =0) 9 :m_x(x), m_y(y){} 10 11 void dis() 12 { 13 cout<<"("<<m_x<<", "<<m_y<<")"<<endl; 14 } 15 16 Complex & operator++(void); 17 protected: 18 double m_x; 19 double m_y; 20 }; 21 22 Complex & Complex::operator++(void) 23 { 24 m_x++; 25 m_y++; 26 return *this; 27 } 28 29 int main() 30 { 31 double a = 1.0; 32 cout<<++a<<endl; 33 ++++a; 34 cout<<a<<endl; 35 36 Complex c1(1.0, 2.0); 37 38 Complex cc = ++c1; 39 cc.dis(); 40 cc = ++++c1; // cc = (c1.operator++()).operator++(); 41 cc.dis(); 42 43 44 return 0; 45 }
結果如下
surfix++
為了區分prefix++和surfix++兩個成員函式,須使用啞元進行區分(引入 啞元,增加了入參的方式,在呼叫時不需要新增任何的引數),其實類似一個佔位符。
1).考慮基本資料型別,以int型別為例,可以進行的操作和不可以進行的操作
1 int b = 1; 2 b++; // 支援 3 b++++; // 不支援
2).先實現基本的語義,程式碼如下
1 Complex operator++(int) 2 { 3 Complex temp = *this; 4 m_x++; 5 m_y++; 6 return temp; 7 }
3)考慮新增
可以觀察到,過載函式返回的是一個臨時物件。若是串聯呼叫,這個臨時物件它又會呼叫一次此過載函式
c1.operator++(0).operator++(0);
呼叫完,然後就消失了。
此時切不可在返回型別中新增&。原因如下:
【不要返回區域性物件的引用或指標】
函式完成後,它所佔用的儲存空間也隨之被釋放掉。因此,函式終止意味著區域性變數的引用將指向不再有效的記憶體區域。同樣地,函式終止,區域性物件被釋放,指標將指向一個不存在的物件。
4)先執行一下,看下是否能編譯通過
我們會發現,第34行無法通過編譯,但是第42行可以通過編譯。
5)過載的運算子是否會導致表示式可以被賦值,應該以基礎型別為準,如int a, b, c; (a=b)=c;是可以的,而(a+b)=c;是不允許的。返回型別通過加const加以限定來實現。
為了使自定義型別與基本資料型別一致,我們在返回型別前面加上const。過載函式中程式碼修改為如下
1 const Complex operator++(int);
修改之後,我們可以看到,第34行和42行均無法通過編譯,符合預期。
2.雙目運算子+
1)考慮基本型別,以下操作都是支援的
1 int a1 = 1, a2 = 2, a3 = 3; 2 int m; 3 m = a1+a2; 4 m = a1+(a2+a3); 5 m = (a1+a2)+a3;
2)先過載=,成員函式如下
1 Complex & Complex::operator=(const Complex &another) 2 { 3 this->m_x = another.m_x; 4 this->m_y = another.m_y; 5 return *this; 6 }
3)再過載運算子+,如下:
因為並未修改傳入的引數,所以引數前加了const
1 Complex Complex::operator+(const Complex &another) 2 { 3 return Complex(this->m_x + another.m_x, this->m_y + another.m_y); 4 }
4)返回型別是否需要加const呢?
我們再對比下表示式的賦值情況,第49行,對於基本型別,臨時物件被賦值的情況編譯無法通過,但是第58行,自定義型別卻編譯通過了。此時,為了使其編譯不過,可通過在返回值型別前加const加以限定。
將程式碼
1 Complex Complex::operator+(const Complex &another);
修改為如下:
1 const Complex Complex::operator+(const Complex &another);
5)此時,發現第49和58行均無法通過編譯,同時第55行和第57行也編譯不過了。
這個是為啥呢?
再仔細看剛修改的程式碼和第57行程式碼。過載函式返回型別加了const後,返回的就是const物件了。第57行程式碼,c1 + c2 + c3; c1 + c2返回的是const物件,而過載函式是一個非const函式。此時,即會報錯。
在const修飾類一節中,有學習過:如果const構成函式過載,const物件只能呼叫const函式,非const物件優先呼叫非const函式。
調整,在過載函式後面新增const,如下:
1 const Complex Complex::operator+(const Complex &another) const;
四.結尾
學無止境,繼續前行,
參考材料
《C++基礎與提高》 王桂林
《C++ Primer》第5版 SB、JL、BE