1. 程式人生 > >C++ 過載操作符 operator

C++ 過載操作符 operator

引數個數的限定

非成員函式

  單目運算子:引數表中只有一個引數;

  雙目運算子:引數表中只有兩個引數

      成員函式

  單目運算子:引數表中沒有引數;

  雙目運算子:引數表中只有一個引數

operator是C++的關鍵字,它和運算子一起使用,表示一個運算子函式,理解時應將operator=整體上視為一個函式名。

   這是C++擴充套件運算子功能的方法,雖然樣子古怪,但也可以理解:一方面要使運算子的使用方法與其原來一致,另一方面擴充套件其功能只能通過函式的方式(c++中,“功能”都是由函式實現的)。

 一、為什麼使用操作符過載?
對於系統的所有操作符,一般情況下,只支援基本資料型別和標準庫中提供的class,對於使用者自己定義的class,如果想支援基本操作,比如比較大小,判斷是否相等,等等,則需要使用者自己來定義關於這個操作符的具體實現。比如,判斷兩個人是否一樣大,我們預設的規則是按照其年齡來比較,所以,在設計person 這個class的時候,我們需要考慮操作符==,而且,根據剛才的分析,比較的依據應該是age。那麼為什麼叫過載呢?這是因為,在編譯器實現的時候,已經為我們提供了這個操作符的基本資料型別實現版本,但是現在他的運算元變成了使用者定義的資料型別class,所以,需要使用者自己來提供該引數版本的實現。

二、如何宣告一個過載的操作符?
A:  操作符過載實現為類成員函式
過載的操作符在類體中被宣告,宣告方式如同普通成員函式一樣,只不過他的名字包含關鍵字operator,以及緊跟其後的一個c++預定義的操作符。
可以用如下的方式來宣告一個預定義的==操作符:
class person{
private:
    int age;
    public:
    person(int a){
       this->age=a;
    }
   inline bool operator == (const person &ps) const;
};
實現方式如下:
inline bool person::operator==(const person &ps) const
{

     if (this->age==ps.age)
        return true;
     return false;
}
呼叫方式如下:
#include
using namespace std;
int main()
{

  person p1(10);
  person p2(20);
  if(p1==p2) cout<<”the age is equal!”< return 0;
}
這裡,因為operator ==是class person的一個成員函式,所以物件p1,p2都可以呼叫該函式,上面的if語句中,相當於p1呼叫函式==,把p2作為該函式的一個引數傳遞給該函式,從而實現了兩個物件的比較。

B:操作符過載實現為非類成員函式(全域性函式)
對於全域性過載操作符,代表左運算元的引數必須被顯式指定。例如:
#include
#include
using namespace std;
class person
{
public:
int age;
public:
};

bool operator==(person const &p1 ,person const & p2)

//滿足要求,做運算元的型別被顯示指定
{
if(p1.age==p2.age)
return true;
return false;
}
int main()
{
person rose;
person jack;
rose.age=18;
jack.age=23;
if(rose==jack)
cout<<"ok"< return 0;
}

C:如何決定把一個操作符過載為類成員函式還是全域性名字空間的成員呢?
①如果一個過載操作符是類成員,那麼只有當與他一起使用的左運算元是該類的物件時,該操作符才會被呼叫。如果該操作符的左運算元必須是其他的型別,則操作符必須被過載為全域性名字空間的成員。
②C++要求賦值=,下標[],呼叫(), 和成員指向-> 操作符必須被定義為類成員操作符。任何把這些操作符定義為名字空間成員的定義都會被標記為編譯時刻錯誤。
③如果有一個運算元是類型別如string類的情形那麼對於對稱操作符比如等於操作符最好定義為全域性名字空間成員。


D:過載操作符具有以下限制:

(1) 只有C++預定義的操作符集中的操作符才可以被過載;

(2)對於內建型別的操作符,它的預定義不能被改變,應不能為內建型別過載操作符,如,不能改變int型的操作符+的含義;

(3) 也不能為內建的資料型別定義其它的操作符;

(4) 只能過載類型別或列舉型別的操作符;

(5) 過載操作符不能改變它們的操作符優先順序;

(6) 過載操作符不能改變運算元的個數;

(7) 除了對( )操作符外,對其他過載操作符提供預設實參都是非法的;

E: 注意點
(1)後果載操操作符首先要確定它的返回值是左值,還是右值,如果是左值最返回引用,如果是右值那就直接返回值;

(2) +號等這樣的操作符沒有物件可以容納改變後值,對於這樣的情況最好返回數值,否則只能要操作符體內建立臨時物件用於容納改變後的值,如果在堆中建立臨時物件返回指標或者引用,在操作符函式體外還需要釋放它,如果返回的物件而不是引用或者指標,那麼效率是比較低的。如果返回的是數值,最好在該類的建構函式中增加對該型別數值的轉換函式,如:返回值是int型別,那麼最好有一個int型別作為引數的建構函式。

(3)在增量運算子中,放上一個整數形參,就是後增量執行符,它是值返回,對於前增量沒有形參,而且是引用返回,示例:

class Test

{

    public:

    Test(x=3){ m_value = x}

    Test &operator ++();   //前增量

    Test &operator ++(int);//後增量

private:

    Int m_value:

};

Test &Test::operator ++()

{

    m_value ++;    //先增量

    return *this;  //返回當前物件

}

Test Test::operator ++(int)

{

    Test tmp(*this);  //建立臨時物件

    m_value ++;       //再增量

    return temp;      //返回臨時物件

}

(4)因為強制轉換是針對基本資料型別的,所以對類型別的轉換需自定義;

(5) 轉換執行符過載宣告形式:operator 型別名();它沒有返回型別,因為型別名就代表了它的返回型別,所以返回型別顯得多餘。

(6)一般來說,轉換運算子與轉換建構函式(即帶一個引數的建構函式)是互逆的,如有了建構函式Test(int),那麼最好有一個轉換運算子int()。這樣就不必提供物件引數過載運算子了,如Test a1(1);Test a2(2); Test a3; a3 = a1+a2;就不需要過載+號操作符了,因為對於a1+a2的運算,系統可能會先找有沒有定義針對Test的+號操作符,如果沒有,它就會找有沒有針對Test類轉換函式引數型別的+號操作符(因為可以將+號執行結果的型別通過轉換函式轉換為Test物件),因為Test類有個int型別的引數,對於int型別有+操作符,所以a1+a2真正執行的是Test(int(a1) + int(a2));即Test(3);

(7)對於轉換運算子,還有一個需要注意的地方就是,如果A類中有以B為引數的轉換函式(建構函式),那B中不能有A的轉換運算子,不然就存在轉換的二義性,如:

class A{A(B&){…}}; class B{ operator A(){…}};那麼以下語句就會有問題:

B b; A(b);//A(b)有就可能是A的建構函式,也可以是B的轉換運算子

怎麼實現運算子的過載?

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

規則:不能過載的運算子有 .  和 .* 和 ?: 和 ::  和 sizeof

友元函式和成員函式的使用場合:一般情況下,建議一元運算子使用成員函式,二元運算子使用友元函式

        1、運算子的操作需要修改類物件的狀態,則使用成員函式。如需要做左值運算元的運算子(如=,+=,++)

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

        3、二元運算子中,第一個運算元為非物件時,必須使用友元函式。如輸入輸出運算子<<和>>

具體規則如下:

運算子

建議使用

所有一元運算子

成員函式

= ( ) [ ]  ->

必須是成員函式

+= -= /= *= ^= &= != %= >>= <<= , 似乎帶等號的都在這裡了.

成員函式

所有其它二元運算子, 例如: –,+,*,/

友元函式

<< >>

必須是友元函式

2. 引數和返回值

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

     對於返回數值的決定:

     1) 如果返回值可能出現在=號左邊, 則只能作為左值, 返回非const引用。

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

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

運算子過載舉例:

+和 -運算子的過載:

  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.     friendconst 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. }  

呼叫:

  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+()

++和--運算子的過載:

  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.     friendconst 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. }  

函式呼叫:

  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可以是任意整數。

3、單目運算子的過載:

      過載運算子函式名:[email protected](引數表)

      隱式呼叫形式:[email protected]  或 @obj1

      顯式呼叫形式:

             成員函式:

                    [email protected]( )//字首

                    [email protected](0)//字尾

             友元函式:

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

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

      執行時,隱式呼叫形式和顯式呼叫形式都會呼叫函式[email protected]()

 過載下標運算子[ ]

  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.     staticint t=0;  
  18.     if (y<5)  
  19.     {  
  20.         return x[y];  
  21.     }  
  22.     else
  23.     {  
  24.         cout<<"下標出界";  
  25.         return t;  
  26.     }     
  27. }  

呼叫:

  1. Point a;