天天看點

運算符重載 字首++ 字尾++

運算符重載:

         運算符與類結合,産生新的含義。 

為什麼要引入運算符重載?

         作用:為了實作類的多态性(多态是指一個函數名有多種含義)

怎麼實作運算符的重載?

方式:類的成員函數 或 友元函數(類外的普通函數)

規則:不能重載的運算符有 .  和 .* 和 ?: 和 ::  和 sizeof

友元函數和成員函數的使用場合:一般情況下,建議一進制運算符使用成員函數,二進制運算符使用友元函數

        1、運算符的操作需要修改類對象的狀态,則使用成員函數。如需要做左值操作數的運算符(如=,+=,++)

        2、運算時,有數和對象的混合運算時,必須使用友元

        3、二進制運算符中,第一個操作數為非對象時,必須使用友元函數。如輸入輸出運算符<<和>>

具體規則如下:

運算符 建議使用
所有一進制運算符 成員函數
= ( ) [ ]  -> 必須是成員函數
+= -= /= *= ^= &= != %= >>= <<= , 似乎帶等号的都在這裡了. 成員函數
所有其它二進制運算符, 例如: –,+,*,/ 友元函數
<< >> 必須是友元函數

2. 參數和傳回值

     當參數不會被改變,一般按const引用來傳遞(若是使用成員函數重載,函數也為const).

     對于傳回數值的決定:

     1) 如果傳回值可能出現在=号左邊, 則隻能作為左值, 傳回非const引用。

     2) 如果傳回值隻能出現在=号右邊, 則隻需作為右值, 傳回const型引用或者const型值。

     3) 如果傳回值既可能出現在=号左邊或者右邊, 則其傳回值須作為左值, 傳回非const引用。

運算符重載舉例:

+和 -運算符的重載:

[cpp]  view plain  copy

  1. class Point    
  2. {    
  3. private:    
  4.     int x;   
  5. public:    
  6.     Point(int x1)  
  7.     {   x=x1;}    
  8.     Point(Point& p)     
  9.     {   x=p.x;}  
  10.     const Point operator+(const Point& p);//使用成員函數重載加号運算符  
  11.     friend const Point operator-(const Point& p1,const Point& p2);//使用友元函數重載減号運算符  
  12. };    
  13. const Point Point::operator+(const Point& p)  
  14. {  
  15.     return Point(x+p.x);  
  16. }  
  17. Point const operator-(const Point& p1,const Point& p2)  
  18. {  
  19.     return Point(p1.x-p2.x);  
  20. }  

調用:

[cpp]  view plain  copy

  1. Point a(1);    
  2. Point b(2);  
  3. a+b;  //正确,調用成員函數  
  4. a-b;  //正确,調用友元函數  
  5. a+1;  //正确,先調用類型轉換函數,把1變成對象,之後調用成員函數  
  6. a-1;  //正确,先調用類型轉換函數,把1變成對象,之後調用友元函數  
  7. 1+a;  //錯誤,調用成員函數時,第一個操作數必須是對象,因為第一個操作數還有調用成員函數的功能  
  8. 1-a;  //正确,先類型轉換 後調用友元函數  

總結:

1、由于+ -都是出現在=号的右邊,如c=a+b,即會傳回一個右值,可以傳回const型值

2、後幾個表達式讨論的就是,數和對象混合運算符的情況,一般出現這種情況,常使用友元函數

3、雙目運算符的重載:

      重載運算符函數名:[email protected](參數表)

      隐式調用形式:obj1+obj2

      顯式調用形式:obj1.operator+(OBJ obj2)---成員函數

                                  operator+(OBJ obj1,OBJ obj2)---友元函數

      執行時,隐式調用形式和顯式調用形式都會調用函數operator+()

++和--運算符的重載:

[cpp]  view plain  copy

  1. class Point    
  2. {    
  3. private:    
  4.     int x;   
  5. public:    
  6.     Point(int x1)  
  7.     {   x=x1;}    
  8.     Point operator++();//成員函數定義自增  
  9.     const Point operator++(int x); //字尾可以傳回一個const類型的值  
  10.     friend Point operator--(Point& p);//友元函數定義--  
  11.     friend const Point operator--(Point& p,int x);//字尾可以傳回一個const類型的值  
  12. };    
  13. Point Point::operator++()//++obj  
  14. {  
  15.     x++;  
  16.     return *this;  
  17. }  
  18. const Point Point::operator++(int x)//obj++  
  19. {  
  20.     Point temp = *this;  
  21.     this->x++;  
  22.     return temp;  
  23. }  
  24. Point operator--(Point& p)//--obj  
  25. {  
  26.     p.x--;  
  27.     return p;  
  28.          //字首形式(--obj)重載的時候沒有虛參,通過引用傳回*this 或 自身引用,也就是傳回變化之後的數值  
  29. }  
  30. const Point operator--(Point& p,int x)//obj--  
  31. {  
  32.     Point temp = p;  
  33.     p.x--;  
  34.     return temp;  
  35.          // 字尾形式obj--重載的時候有一個int類型的虛參, 傳回原狀态的拷貝  
  36. }  

函數調用:

[cpp]  view plain  copy

  1. <pre class="cpp" name="code">Point a(1);  
  2. Point b(2);  
  3. a++;//隐式調用成員函數operator++(0),字尾表達式  
  4. ++a;//隐式調用成員函數operator++(),字首表達式  
  5. b--;//隐式調用友元函數operator--(0),字尾表達式  
  6. --b;//隐式調用友元函數operator--(),字首表達式  
  7. cout<<a.operator ++(2);//顯式調用成員函數operator ++(2),字尾表達式  
  8. cout<<a.operator ++();//顯式調用成員函數operator ++(),字首表達式  
  9. cout<<operator --(b,2);//顯式調用友元函數operator --(2),字尾表達式  
  10. cout<<operator --(b);//顯式調用友元函數operator --(),字首表達式 </pre>  

 總結:

1、a++

       函數傳回:temp(臨時變量)

       函數傳回是否是const類型:傳回是一個拷貝後的臨時變量),不能出現在等号的左邊(臨時變量不能做左值),函數的結果隻能做右值,則要傳回一個const類型的值

      ++a

       函數傳回:*this;

      函數傳回是否是const類型:傳回原狀态的本身,傳回值可以做左值,即函數的結果可以做左值,則要傳回一個非const類型的值

2、前字尾僅從函數名(operator++)無法區分,隻能有參數區分,這裡引入一個虛參數int x,x可以是任意整數。

關于字首++和字尾++的進一步說明:

對于疊代器和其他模闆對象使用字首形式 (++i) 的自增, 自減運算符.,理由是 前置自增 (++i) 通常要比後置自增 (i++) 效率更高。于是我查了查前置++和後置++的差別。

注意:《more effective c++》條款8也專門叙述了問題。後來我發現,下面的文章基本就是它的翻版,哈哈

前置++和後置++的差別

int a = 0;
++ a;   //前置++
a++;    //後置++
           

《C專家程式設計》中有如下描述(P276,人民郵電出版社):

++a表示取a的位址,增加它的内容,然後把值放在寄存器中;

a++表示取a的位址,把它的值裝入寄存器,然後增加記憶體中的a的值;

另外,網上找了篇文章,通過從運算符重載的角度來探讨他們的不同,如下:

假設有一個類Age,描述年齡。該類重載了前置++和後置++兩個操作符,以實作對年齡的自增。

class Age   
	{   
	public:   
	  
	    Age& operator++() //前置++   
	    {   
	        ++i;   
	        return *this;   
	    }   
	  
	    const Age operator++(int) //後置++   
    {   
	        Age tmp = *this;   
	        ++(*this);  //利用前置++   
	        return tmp;   
	    }   
	  
	    Age& operator=(int i) //指派操作   
	    {   
	        this->i = i;   
	        return *this;   
	    }   
	  
	private:   
	    int i;   
	};  
           

從上述代碼,我們可以看出前置++和後置++,有3點不同:

  1. 傳回類型不同
  2. 形參不同
  3. 代碼不同
  4. 效率不同

傳回值類型的差別

前置++的傳回類型是Age&,後置++的傳回類型const Age。這意味着,前置++傳回的是左值,後置++傳回的是右值。(關于左值和右值的讨論很多,見本文下面)

左值和右值,決定了前置++和後置++的用法。

int main()   
	{   
	    Age a;   
	  
	    (a++)++;  //編譯錯誤   
	    ++(a++);  //編譯錯誤   
	    a++ = 1;   //編譯錯誤   
	    (++a)++;  //OK   
	    ++(++a);  //OK   
	    ++a = 1;   //OK   
	}  
           

++的類型是const Age,自然不能對它進行前置++、後置++、指派等操作。

++a的類型是Age&,當然可以對它進行前置++、後置++、指派等操作

a++的傳回類型為什麼要是const對象呢?

有兩個原因:

  1. 如果不是const對象,a(++)++這樣的表達式就可以通過編譯。但是,其效果卻違反了我們的直覺 。a其實隻增加了1,因為第二次自增作用在一個臨時對象上。
  2. 另外,對于内置類型,(i++)++這樣的表達式是不能通過編譯的。自定義類型的操作符重載,應該與内置類型保持行為一緻 。

a++的傳回類型如果改成非const對象,肯定能通過編譯,但是我們最好不要這樣做。

++a的傳回類型為什麼是引用呢?

這樣做的原因應該就是:與内置類型的行為保持一緻。前置++傳回的總是被自增的對象本身。是以,++(++a)的效果就是a被自增兩次。

形參的差別

前置++沒有形參,而後置++有一個int形參,但是該形參也沒有被用到。很奇怪,難道有什麼特殊的用意?

其實也沒有特殊的用意,隻是為了繞過文法的限制。

前置++與後置++的操作符重載函數,函數原型必須不同。否則就違反了“重載函數必須擁有不同的函數原型”的文法規定。

雖然前置++與後置++的傳回類型不同,但是傳回類型不屬于函數原型。為了繞過文法限制,隻好給後置++增加了一個int形參。

原因就是這麼簡單,真的沒其他特殊用意。其實,給前置++增加形參也可以;增加一個double形參而不是int形參,也可以。隻是,當時就這麼決定了。

代碼實作的差別

前置++的實作比較簡單,自增之後,将*this傳回即可。需要注意的是,一定要傳回*this。

後置++的實作稍微麻煩一些。因為要傳回自增之前的對象,是以先将對象拷貝一份,再進行自增,最後傳回那個拷貝。

在Age的代碼中,後置++利用了前置++來實作自增。這樣做是為了避免“自增的代碼”重複。

在本例中,自增的代碼很簡單,就是一行++i,沒有必要這樣做。但是在其它自增邏輯複雜的例子中,這麼做還是很有必要的。

效率的差別

如果不需要傳回自增之前的值,那麼前置++和後置++的計算效果都一樣。但是,我們仍然應該優先使用前置++,尤其是對于使用者自定義類型的自增操作。

前置++的效率更高,理由是:後置++會生成臨時對象。

從Age的後置++的代碼實作也可以看出這一點。

const Age operator++(int) //後置++   
	{   
	    Age tmp = *this;   
	    ++(*this);  //利用前置++   
	    return tmp;   
	}  
           

很明顯,tmp是一個臨時對象,會造成一次構造函數和一次析構函數的額外開銷。雖然,編譯器在某些情況下可以優化掉這些開銷。但是,我們最好不要依賴編譯器的行為。

是以,在非内置類型的時候,盡量使用前置++,因為效率高(後置自增,效率低)

3、單目運算符的重載:

      重載運算符函數名:[email protected](參數表)

      隐式調用形式:[email protected]  或 @obj1

      顯式調用形式:

             成員函數:

                    [email protected]( )//字首

                    [email protected](0)//字尾

             友元函數:

                    [email protected](OBJ obj)//字首

                    [email protected](OBJ obj,int x)//字尾

      執行時,隐式調用形式和顯式調用形式都會調用函數[email protected]()

 重載下标運算符[ ]

[cpp]  view plain  copy

  1. class Point    
  2. {    
  3. private:    
  4.     int x[5];   
  5. public:    
  6.     Point()  
  7.     {  
  8.         for (int i=0;i<5;i++)  
  9.         {  
  10.             x[i]=i;  
  11.         }  
  12.     }   
  13.     int& operator[](int y);  
  14. };    
  15. int& Point::operator[](int y)  
  16. {  
  17.     static int t=0;  
  18.     if (y<5)  
  19.     {  
  20.         return x[y];  
  21.     }  
  22.     else  
  23.     {  
  24.         cout<<"下标出界";  
  25.         return t;  
  26.     }     
  27. }  

調用:

[cpp]  view plain  copy

  1. Point a;  
  2. for (int i=0;i<10;i++)  
  3. {  
  4.          cout<<a[i]<<endl;//無論i下标是否越界,每當使用a[i]時,都會調用[]的重載  
  5. }  
  6. a[0]=10;  

重載下标運算符[ ]的目的:

          1、對象[x]  類似于 數組名[x],更加符合習慣

          2、可以對下标越界作出判斷

文法:

        重載方式:隻能使用成員函數重載

        函數名:operator[ ](參數表)

        參數表:一個參數,且僅有一個參數,該參數設定了下标值,通常為整型,但是也可以為字元串( 看成下标)。

        函數調用:顯式調用:Obj[arg]-對象[下标]

                              隐式調用:obj.operator[ ](arg)  

        傳回類型:

               1、傳回函數引用 + 傳回成員的實際類型(由程式員根據函數體定義)

               2、因為傳回值可以做左值和右值,應該不使用傳回值為const類型

                     但是,為了能通路const對象,下标運算符重載有非const和const兩個版本。(待定寫)

 如:int&  Point::operator[](int y)//為什麼使用傳回引用:傳回的值可以做左值,也可以做右值,則必須使用傳回引用

 重載運算符( )

[cpp]  view plain  copy

  1. class Point    
  2. {    
  3. private:    
  4.     int x;   
  5. public:    
  6.     Point(int x1)  
  7.     {   x=x1;}    
  8.     const int operator()(const Point& p);  
  9. };    
  10. const int Point::operator()(const Point& p)  
  11. {  
  12.     return (x+p.x);  
  13. }  

[cpp]  view plain  copy

  1. 調用:  
  2. Point a(1);  
  3. Point b(2);  
  4. cout<<a(b);  

重載運算符( )的目的:

          1、對象( )  類似于 函數名(x),更加符合習慣

文法:

        重載方式:隻能使用成員函數重載

        重載後還可以繼續重載

        函數名:operator( )(參數表)

        參數表:參數随意,具體根據實際情況而定。

        函數調用:顯式調用:Obj(x)

                            隐式調用:obj.operator( )(x)  

        傳回類型:

               1、傳回成員的實際類型随意,具體由程式員根據函數體定義

               2、因為傳回值隻能做右值,隻讀,應該使用傳回值為const類型

重載輸入輸出操作符<< >>

[cpp]  view plain  copy

  1. class Point    
  2. {    
  3. private:    
  4.     int x;   
  5. public:    
  6.     Point(int x1)  
  7.     {   x=x1;}   
  8.     friend ostream& operator<<(ostream& cout,const Point& p);//使用友元函數重載<<輸出運算符  
  9.     friend istream& operator>>(istream& cin,Point& p);//使用友元函數重載>>輸出運算符  
  10. };    
  11. ostream& operator<<(ostream& cout,const Point& p)  
  12. {  
  13.     cout<<p.x<<endl;  
  14.     return cout;  
  15. }  
  16. istream& operator>>(istream& cin,Point& p)  
  17. {  
  18.     cin>>p.x;  
  19.     return cin;  
  20. }  

[cpp]  view plain  copy

  1. 調用:  
  2. Point a(1);  
  3. Point b(2);  
  4. cin>>a>>b;  
  5. cout<<a<<b<<endl;   

文法:

重載方式:隻能使用友元函數重載 且 使用三個引用&

函數名:

       輸出流: operator<<(參數表)

       輸入流:operator>>(參數表)

參數表:固定(容易出錯啊),兩個參數均用引用&

       輸出流: 必須是兩個參數:對輸出流ostream& 和 對象

                        第一個操作數cout,定義在檔案iostream中,是标準類類型ostream的對象的引用。

                        如:ostream& cout,const Point& p

       輸入流:必須是兩個參數:對輸入流ostream& 和 對象

                       第一個操作數是cin,定義在檔案iostream,實際上是标準類類型istream的對象的引用

                       如:instream& cin,const Point& p

函數調用:

       輸出流: 顯式調用:cout<<對象

                        隐式調用: operator<<(cout,對象)

       輸入流:顯式調用:cin>>對象

                        隐式調用: operator>>(cin,對象)

傳回類型:傳回類型固定 + 使用傳回函數引用(滿足連續輸出)

       輸出流: 傳回ostream&

                        如:ostream& operator<<(ostream& cout,const Point& p)

       輸入流:傳回:istream&

                        如:istream& operator>>(istream& cin,Point& p)

注意:為什麼輸入輸出操作符的重載必須使用友元函數?

因為:成員函數要求是有對象調用,則第一個參數必須是類的對象,但是<<和>>第一個參數是流的對象引用。

故,不能使用成員函數