天天看點

【Journey to the Cpp】Series之運算符重載關卡一:運算符重載(operator overloading)

關卡一:運算符重載(operator overloading)

故事背景:也許你沒有感覺到,其實我們已經在C++中使用【運算符重載】了。事實上計算機處理整數、單精度數和雙精度數加法的操作方法是很不相同的,但由于C++已經對運算符“+”進行了重載,是以“+”就能适用于int,float,double類型的不同運算。另外,我們使用的流插入運算符(<<)與流提取運算符(>>),在C++中也已經進行了重載,這就是頭檔案中“iostream”的由來。

      說了這麼多,那麼運算符重載究竟是幹什麼的?又是怎樣使用的?

      前面已經提到,我們可以對标準類型的資料進行四則運算,但不能對諸如複數等類型的資料進行四則運算,這裡運算符重載函數就登上舞台了。

☆副本:

#PartⅠ 對運算符重載的方法

重載運算符的函數一般格式如下:

函數類型 operator運算符名稱(形參表)

{對運算符的重載處理}

      例如我想利用“+”實作兩個複數的相加時,可以如下定義:

Complex operator+(Complex &c1,Complex &c2);

注:“operator”是關鍵字,operator和運算符組成函數名,如上面的“operator+”是函數名,在這裡的意思是“對運算符+重載的函數”。這裡需要注意的是重載的運算符應該是C++中已有的運算符。

Part Ⅱ 重載運算符的規則

1.C++不允許使用者自己定義新的運算符,隻能對已有的C++運算符進行重載。

2.C++不允許重載的運算符有5個:(1)成員運算符 “.”;(2)成員指針通路運算符(不是乘号)“*”(3)域運算符 “::”;(4)長度運算符 “sizeof”;(5)條件運算符 “?:”。是以,隻要記住這些不能重載的就可以了。

3.重載不能改變運算符運算對象(即操作數)的個數。例如“>”是進行兩個數的大小比較,是以其操作數必須為兩個。

4.重載不能改變運算符的優先級别。例如“/”優先于“-”。隻能通過加圓括号的辦法強制改變重載運算符的運算順序。

5.重載不能改變運算符的結合性。如指派運算符“=”是自左向右結合的,其重載後仍自左向右結合。

6.重載運算符的函數不能有預設的參數,否則就改變了運算符參數的個數。

7.重載的運算符必須和使用者定義的自定義類型的對象一起使用,其參數至少應有一個是類的對象或類對象的引用。即運算符重載函數的參數不能都是标準類型的資料。

8.用于類對象的運算符一般必須重載,但運算符“=”和“&”不必使用者重載。

      (1)指派運算符“=”可以用于每一個類的對象,可以利用它在同類對象之間互相指派。

      (2)位址運算符“&”也不必重載,它能傳回類對象在記憶體中的起始位址。

9.理論上可以将一個運算符重載為執行任意操作,但為了使程式的可讀性更高,應當将運算符重載為能實作類似于标準類型資料所能實作的功能,如“+”重載後可以實作兩個複數的相加,而不重載為兩個複數相乘。

Part Ⅲ 運算符重載函數的處理方式

類的成員函數、友元函數和普通函數

1.類的成員函數

定義:Complex operator+(Complex &c2);

      這時你可能會有疑問,複數的相加不是應該有兩個操作數嗎?其實,在運算符重載函數定義為成員函數後,有一個參數是隐含的,這是因為有一個this指針隐式地通路類對象成員。編譯系統做出的了解是c1.operator(c2)(假設c1和c2已經定義),即通過對象c1調用運算符重載函數“operator+”,并以表達式中第2個參數(上式中的c2)參數作為函數實參。

2.友元函數

定義:Complex operator+(Complex &c1,Complex &c2);

我們可以清楚地看到,友元函數與上述的成員函數的差別是,友元函數有兩個形參。這時,編譯系統做出的了解是,operator+(c1,c2)。

3.普通函數

      由于在類外定義的函數不能通路類的私有資料成員,是以在把運算符重載函數定義為普通函數時,需将類的資料成員定義為公有的,這破壞了資料的隐蔽性原則,是以這種方法很少使用,在此不再贅述,下面會有樣例。

讨論:究竟是使用成員函數好還是使用友元函數好呢?我們做出以下的探究

(1)如果将運算符重載函數作為成員函數,它可以通過this指針自由的通路本類的資料成員,是以可以少寫一個函數的參數。但必須要求運算表達式中第一個參數(運算符左側的操作數)是一個類的對象,而且與運算符函數的類型相同。因為必須通過類的對象去調用該類的成員函數,而且隻有運算符重載函數傳回值與該對象同類型,運算結果才有意義。

(2)将雙目運算符重載為友元函數時,由于友元函數不是該類的成員函數,是以在函數的形參表列中必須有兩個參數,不能省略。

(3)從以上的分析可以看出,無論是成員函數還是友元函數,都不适用數學上的交換律。

(4)由于友元函數的使用會破壞類的封裝,是以從原則上說,要盡量将運算符函數作為成員函數。

(5)下面給出成員函數和友元函數的一般适用範圍

   ①C++規定,指派運算符=、下标運算符[ ]、函數調用運算符( )、成員運算符->必須作為成員函數。

   ②流插入運算符“<<”和流提取運算符“>>”、類型轉換運算符不能定義為類的成員函數,隻能定義為友元函數。

   ③一般将單目運算符和複合運算符(+=、!=、%=等)定義為成員函數。

   ④一般将雙目運算符定義為友元函數。

4.運算符重載函數的三種形式之執行個體:

題目:重載“+”,使之能實作兩個複數的相加。

(1)成員函數形式:

#include <iostream>
using namespace std;
class Complex
{
private:
    double real,image;            //定義複數的實部與虛部
public:
    Complex(double r=0,double i=0)//定義構造函數
    {
        real=r;
        image=i;
    }
    void display()                //輸出複數
    {
        cout<<"("<<real<<","<<image<<"i"<<")"<<endl;
    }
   Complex operator+(Complex &c2);//重載函數聲明為成員函數的形式
};
   Complex Complex::operator+(Complex &c2)//定義成員函數
   {
        Complex c;
        c.real=real+c2.real;
        c.image=image+c2.image;
        return c;
   }
   int main()
   {
       Complex com1(1,2),com2(1,2),com3;
       com3=com1+com2;         //實作兩個複數的相加
       com3.display();
       return 0;
   }

           

(2)友元函數形式:

#include <iostream>
using namespace std;
class Complex
{
private:
    double real,image;
public:
    Complex(double r=0,double i=0)
    {
        real=r;
        image=i;
    }
    void display()
    {
        cout<<"("<<real<<","<<image<<"i"<<")"<<endl;
    }
   friend Complex operator+(Complex &c1,Complex &c2);//重載函數聲明為友元函
};                                                   //數的形式
   Complex operator+(Complex &c1,Complex &c2)//本例中友元函數必須有兩個形參
   {
        Complex c;
        c.real=c1.real+c2.real;
        c.image=c1.image+c2.image;
        return c;
   }
   int main()
   {
       Complex com1(1,2),com2(1,2),com3;
       com3=com1+com2;
       com3.display();
       return 0;
   }

           

(3)普通函數形式:

#include <iostream>
using namespace std;
class Complex
{
public:               //重載函數為普通函數時,資料成員必須定義為公有的,否則
    double real,image;//該普通函數将無法調用類的資料成員
    Complex(double r=0,double i=0)
    {
        real=r;
        image=i;
    }
    void display()
    {
        cout<<"("<<real<<","<<image<<"i"<<")"<<endl;
    }
};
   Complex operator+(Complex &c1,Complex &c2)//本例中普通函數必須有兩個形參
   {
        Complex c;
        c.real=c1.real+c2.real;
        c.image=c1.image+c2.image;
        return c;
   }
   int main()
   {
       Complex com1(1,2),com2(1,2),com3;
       com3=com1+com2;
       com3.display();
       return 0;
   }

           

☆主線

      運算符的重載可以包括雙目運算符重載和單目運算符重載,下面我将分開來介紹,介紹形式以代碼執行個體為主。

PartⅠ 雙目運算符的重載

導語:雙目運算符(或稱為二進制運算符)是C++中最常用的運算符。雙目運算符有兩個操作數(參與運算的資料),通常在運算符的左右兩側,如a+b,a<b等。

執行個體:聲明一個字元串類String,用來存放不定長的字元串,重載運算符“==”,“<”和“>”,使它們能用于兩個字元串的等于、小于和大于的比較。

#include <iostream>
#include <string>
#include <cstring>   //使用strcmp函數必須使用該頭檔案
using namespace std;
class String
{
public:
    String(){p=NULL;}//定義預設構造函數
    String(const char *str);//聲明構造函數
    void display();
    friend bool operator>(String &st1,String &st2);//聲明“>”的重載函數
    friend bool operator<(String &st1,String &st2);//聲明“<”的重載函數
    friend bool operator==(String &st1,String &st2);//聲明“==”的重載函數
private:                                     //此處必須為“==”,“=”為指派号
    const char *p;//字元型指針,用于指向字元串
};
String::String(const char *str)//定義構造函數
{
    p=str;
}
void String::display()//輸出p所指向的字元串
{
    cout<<p;
}
bool operator>(String &st1,String &st2)//定義“>”的重載函數
{
    if(strcmp(st1.p,st2.p)>0)
        return true;
    else
        return false;
}
bool operator<(String &st1,String &st2)//定義“<”的重載函數
{
    if(strcmp(st1.p,st2.p)<0)
        return true;
    else
        return false;
}
bool operator==(String &st1,String &st2)//定義“==”的重載函數
{
    if(strcmp(st1.p,st2.p)==0)
        return true;
    else
        return false;
}
void Compare(String &st1,String &st2)//用于兩個字元串的比較,減輕主函數的負擔
{                                    //同時使程式可讀性更高
    if(operator>(st1,st2)==1)
    {
        st1.display();cout<<">";st2.display();
    }
    else if(operator<(st1,st2)==1)
    {
        st1.display();cout<<"<";st2.display();
    }
    else if(operator==(st1,st2)==1)
    {
        st1.display();cout<<"=";st2.display();
    }
    cout<<endl;
}
int main()
{
    String st1("welcome"),st2("come"),st3("home"),st4("welcome");//定義對象
    Compare(st1,st2);//字元串比較
    Compare(st2,st3);//字元串比較
    Compare(st1,st4);//字元串比較
    return 0;
}
           

      通過分析上面的這個執行個體,我相信大家會對雙目運算符的了解更加深刻,并且使用更加熟練。

PartⅡ 單目運算符的重載

      單目運算符中自增自減運算符應用較多,是以我們在這裡以自增運算符(自減運算符與此類似)為例。

      我們知道自增分為前置自增與後置自增,那麼我們在運算符重載中應該怎樣區分呢?在C++中,我們在自增運算符重載函數中,增加一個int型形參(該形參不需确切的名稱,隻寫int即可),得到的是後置自增,這樣就可以區分前置自增與後置自增了。

導語:前置自增是先自加,傳回的是修改後的對象本身;後置自增傳回的是自加前的對象,然後對象自加。

執行個體:有一個Time類,包含資料成員min(分)和sec(秒),模拟秒表,每次走一秒,滿60秒進一分鐘,此時秒又從0開始算。要求輸出分和秒的值。

#include <iostream>
using namespace std;
class Time
{
public:
    Time(){min=0;sec=0;}//定義構造函數
    Time(int m,int s):min(m),sec(s){}//定義構造函數
    void display();//聲明輸出時間的函數
    Time operator++();//聲明前置自增運算符的重載函數
    Time operator++(int);//聲明後置自增運算符的重載函數,形參中必須有“int”
private:
    int min,sec;
};
void Time::display()//定義輸出時間的函數
{
    cout<<"Minute: "<<min<<";"<<"Second: "<<sec<<endl;
}
Time Time::operator++()//定義前置自增運算符的重載函數
{
    if(++sec>=60)
    {
        sec-=60;
        min+=1;
    }
    return *this;//傳回自加後的目前對象
}
Time Time::operator++(int)//定義後置自增運算符的重載函數
{
    Time temp(*this);//建立臨時對象temp
    sec++;
    if(sec>=60)
    {
        sec-=60;
        min+=1;
    }
    return temp;//傳回的是自加前的對象
}
int main()
{
    Time t1(18,30),t2;
    ++t1;             //調用前置自增運算符的重載函數
    t1.display();     
    t2=t1++;          //t2儲存的是執行t1++之前的t1的值
    t2.display();
    t1.display();     //執行t1++後的t1的值
    return 0;
}

           

      當然,單目運算符也可以定義為友元函數,大家可以嘗試一下。

      通過對運算符重載的分析我們可以看出,運算符重載函數的出現友善了進行不同對象間的運算。由于我們有時候在程式中輸出的不止是标準類型的資料,是以對流插入和流提取運算符的重載是非常有必要的,在續集中我将介紹這兩個運算符的重載。

      了解了運算符重載後,你可能會有疑惑,程式還能執行運算符本來的含義嗎?如果能執行,那麼編譯系統又是怎樣識别的呢?關于這點,我同樣會在續集中做出詳細的解釋。

      我們續集再回!

繼續閱讀