官术网_书友最值得收藏!

2.5 運算符重載

盡管C++語言有豐富的數據類型和運算符,但仍然不能滿足應用編程的一些需要,復數及其操作就是這樣的一個例子。雖然用戶可以定義一個復數類,然后利用成員函數實現數據之間的運算操作,但總沒有運算符操作來得更為簡捷。

運算符重載就是賦予已有的運算符多重含義,是一種靜態聯編的多態。通過重新定義運算符,使其能夠用于特定類對象執行特定的功能,從而增強了C++語言的擴充能力。

2.5.1 運算符重載函數

事實上,C++運算符本身具有簡單的多態能力,例如算術運算符可以用于整型、浮點型等混合的操作對象。但對于類對象的運算操作,許多運算符的操作就顯得能力不足了。為此,需要對運算符進行重載。也就是說,運算符重載的目的是實現類對象的運算操作。

重載時,一般是在類中定義一個特殊的函數,以便通知編譯器,遇到該重載運算符時調用該函數,并由該函數來完成該運算符應該完成的操作。這種特殊的函數稱為運算符重載函數,它通常是類的成員函數或友元函數,運算符的操作數通常也是該類的對象。

在類中,定義一個運算符重載函數與定義一般成員函數相類似,只不過函數名必須以operator開頭,其一般形式如下:

<函數類型><類名>::operator <重載的運算符>(<形參表>)
{ … }                           // 函數體

運算符重載函數的函數是以特殊的關鍵字開始的,編譯很容易與其他的函數名區分開來。這里先來看一個實例,它用來定義一個復數類CComplex,然后重載“+”運算符,使這個運算符能直接完成復數的加運算。

【例Ex_Complex】 運算符的簡單重載

#include <iostream.h>
class CComplex
{
public:
    CComplex(double r = 0, double i = 0)
    {
        realPart=r;        imagePart=i;
    }
    void print()
    {
        cout<<"實部 ="<<realPart<<", 虛部 ="<<imagePart<<endl;
    }
    CComplex operator+(CComplex&c);        // 重載運算符+
    CComplex operator+(double r);          // 重載運算符+
private:
    double realPart;                       // 復數的實部
    double imagePart;                      // 復數的虛部
};
CComplex CComplex::operator+(CComplex&c)   // 參數是CComplex引用對象
{
    CComplex temp;
    temp.realPart = realPart + c.realPart;
    temp.imagePart = imagePart + c.imagePart;
    return temp;
}
CComplex CComplex::operator+(double r)      // 參數是double型數據
{
    CComplex temp;
    temp.realPart = realPart + r;
    temp.imagePart = imagePart;
    return temp;
}
int  main()
{
    CComplex c1(12,20), c2(50,70), c;
    c=c1+c2;        c.print();
    c=c1+20;        c.print();
    return 0;
}

程序運行結果如下:

實部 = 62, 虛部 = 90

實部 = 32, 虛部 = 20

分析:

(1)程序中,對運算符“+”作了兩次重載,一個用于實現兩個復數的加法,另一個用于實現一個復數與一個實數的加法。

(2)從main函數中的對象表達式可以看出,經重載后的運算符的使用方法與普通運算符基本一樣。但編譯總會自動完成相應的運算符重載函數的調用過程。例如表達式“c = c1 + c2”,編譯首先將“c1 + c2”解釋為“c1.operator + (c2)”,從而調用運算符重載函數operator + (CComplex&c),然后再將運算符重載函數的返回值賦給c。同樣,對于表達式“c = c1 + 20”,編譯器將“c1+ 20”解釋為“c1.operator + (20)”,調用運算符重載函數operator + (double r) ,然后再將運算符重載函數的返回值賦給c。

(3)在編譯解釋“c1 + c2”時,由于成員函數都隱含一個this指針,因此解釋的“c1.operator+ (c2)”等價于“operator + (&c1, c2)”。正是因為this指針的存在,當重載的運算符函數是類的成員函數時,運算符函數的形參個數要比運算符操作數個數少一個。對于雙目運算符(如“+”)重載的成員函數來說,它應只有一個參數,用來指定其右操作數。而對于單目運算符重載的成員函數來說,由于操作數就是該類對象本身,因此運算符函數不應有參數。

需要說明的是:運算符重載函數的返回值類型和參數取決于運算符的含義和結果,它們可能是類、類引用、類指針或其他類型。

2.5.2 運算符重載限制

在C++中,運算符重載還有以下一些限制:

(1)重載的運算符必須是一個已有的合法的C++運算符,如“+”、“-”、“*”、“/”、“++”等,且不是所有的運算符都可以重載。在C++中不允許重載的運算符有?:(條件)、.(成員)、*.(成員指針)、::(域作用符)、sizeof(取字節大小)。

(2)不能定義新的運算符,或者說,不能為C++沒有的運算符進行重載。

(3)當重載一個運算符時,該運算符的操作數個數、優先級和結合性不能改變。

(4)運算符重載的方法通常有類的操作成員函數和友元函數兩種,但=(賦值)、()(函數調用)、[](下標)和->(成員指針)運算符不能重載為友元函數。

2.5.3 友元重載

友元重載方法既可用于單目運算符,也可以用于雙目運算符,其一般格式如下:

friend<函數類型>operator<重載的運算符>(<形參>)          // 單目運算符重載
{ … }                           // 函數體
friend<函數類型>operator<重載的運算符>(<形參1, 形參2>)  // 雙目運算符重載
{ … }                           // 函數體

其中,對于單目運算符的友元重載函數來說,只有一個形參,形參類型既可能是類的對象,也可能是類的引用,這取決于不同的運算符。對于“++”、“--”等來說,這個形參類型是類的引用對象,因為操作數必須是左值。對于單目“-”(負號運算符)等來說,形參類型可以是類的引用,也可以是類的對象。對于雙目運算符的友元重載函數來說,它有兩個形參,這兩個形參中必須有一個是類的對象。

下面來看一個實例,這個例子是在【例Ex_Complex】的基礎上,用友元函數實現雙目運算符“+”、單目運算符“-”的重載,而用成員函數實現“+=”運算。

【例Ex_ComplexFriend】 運算符的友元重載

#include <iostream.h>
class CComplex
{
public:
    CComplex(double r = 0, double i = 0)
    {
        realPart=r;    imagePart=i;
    }
    void print()
    {
        cout<<"實部 ="<<realPart<<", 虛部 ="<<imagePart<<endl;
    }
    CComplex operator+(CComplex&c);                     //A重載運算符+
    CComplex operator+(double r);                       //B重載運算符+
    friend    CComplex operator+(double r,CComplex&c);  //C友元重載運算符+
    friend    CComplex operator-(CComplex&c);           // 友元重載單目運算符-
    void operator += (CComplex &c);
private:
    double realPart;                                    // 復數的實部
    double imagePart;                                   // 復數的虛部
};
CComplex CComplex::operator + (CComplex &c)
{
    CComplex temp;
    temp.realPart = realPart + c.realPart;
    temp.imagePart = imagePart + c.imagePart;
    return temp;
}
CComplex CComplex::operator + (double r)
{
    CComplex temp;
    temp.realPart = realPart + r;
    temp.imagePart = imagePart;
    return temp;
}
CComplex operator + (double r, CComplex &c)
{
    CComplex temp;
    temp.realPart = r + c.realPart;
    temp.imagePart = c.imagePart;
    return temp;
}
CComplex operator - (CComplex &c)
{
    return CComplex(-c.realPart, -c.imagePart);
}
void CComplex::operator += (CComplex &c)
{
    realPart+=c.realPart;     imagePart+=c.imagePart;
}
int  main()
{
    CComplex c1(12,20), c2(30,70), c;
    c=c1+c2;           c.print();
    c=c1+20;           c.print();
    c=20+c2;           c.print();
    c2+=c1;            c2.print();
    c1=-c1;            c1.print();
    return 0;
}

程序運行結果如下:

實部 = 42, 虛部 = 90

實部 = 32, 虛部 = 20

實部 = 50, 虛部 = 70

實部 = 42, 虛部 = 90

實部 = -12, 虛部 = -20

分析和比較:

(1)類CComplex中,對于雙目運算符“+”的重載分別用成員函數和友元函數的方法定義了3個重載函數,如代碼注釋中所標的A、B和C。

(2)當“c = 20+c2”時,這里的“20+c2”是無法解釋成“20.operator + (c2)”的,因為“20”不是一個對象。因此,當左操作數是數值而不是對象時,是無法用成員函數來實現運算符重載的,必須用友元函數才能實現。正因為這種區別,當“c = 20+c2”時會自動調用A版本的友元運算符重載函數。

(3)類似地,當“c = c1+c2”時,這里的“c1+c2”可以被編譯解釋為“c1.operator + (c2)”,即“operator +(&c1, c2)”,顯然,它會調用A版本的運算符重載函數。若此時用友元定義這樣的運算符“+”的重載函數:

friend    CComplex operator+(CComplex&c1,CComplex c2);

則會與A版本的運算符重載函數相沖突,出現二義性。因此,運算符重載要避免二義性的產生。

2.5.4 轉換函數

類型轉換是將一種類型的值映射為另一種類型的值。C++的類型轉換包含自動隱含和強制轉換兩種方法。轉換函數是實現強制轉換操作的手段之一,它是類中定義的一個非靜態成員函數,其一般格式為:

class <類名>
{
public:
    operator <類型>();
    //…
}

其中,類型是要轉換后的一種數據類型,它可以是基本數據類型,也可以是構造數據類型。operator和類型一起構成了轉換函數名,它的作用是將“class <類名>”聲明的類對象轉換成類型指定的數據類型。當然,轉換函數既可以在類中定義也可在類體外實現,但聲明必須在類中進行,因為轉換函數是類中的成員函數。

下面來看一個示例,它將金額的小寫形式(數字)轉換成金額的大寫形式(漢字)。

【例Ex_Money】 轉換函數的使用

#include <iostream.h>
#include <string.h>
typedef char* USTR;
class CMoney
{
    double amount;
public:
    CMoney(double a = 0.0) { amount = a; }
    operator USTR ();
};
CMoney::operator USTR ()
{
    USTR basestr[15] = {"分", "角", "元", "拾", "佰", "仟", "萬",
                "拾", "佰", "仟", "億", "拾", "佰", "仟", "萬"};
    USTR datastr[10] = {"零", "壹", "貳", "叁", "肆", "伍", "陸", "柒", "捌", "玖"};
    static char strResult[80];
    double temp, base = 1.0;
    int n = 0;
    temp = amount * 100.0;
    strcpy(strResult, "金額為: ");
    if (temp < 1.0)
        strcpy(strResult,"金額為: 零元零角零分");
    else
    {
        while (temp>= 10.0)
        {
            // 計算位數
            base=base*10.0;   temp=temp/10.0;   n++;
        }
        if(n>=15)  strcpy(strResult,"金額超過范圍!");
        else
        {
            temp = amount * 100.0;
            for (int m=n; m>=0; m--)
            {
                int d = (int)(temp / base);
                temp=  temp-base*(double)d;
                base=  base/10.0;
                strcat(strResult, datastr[d]);
                strcat(strResult, basestr[m]);
            }
        }
    }
    return strResult;
}
int  main()
{
    CMoney money(1234123456789.123);
    cout<<(USTR)money<<endl;
    return 0;
}

程序中,轉換的類型是用typedef定義的USTR類型。調用該轉換函數是直接采用強制轉換方式,如程序中的(USTR)money或USTR(money)。程序運行的結果如下:

金額為:壹萬貳仟叁佰肆拾壹億貳仟叁佰肆拾伍萬陸仟柒佰捌拾玖元壹角貳分

需要說明的是:轉換函數重載用來實現類型轉換的操作,但轉換函數只能是成員函數,而不能是友元函數。轉換函數可以被派生類繼承,也可以被說明為虛函數,且在一個類中可以定義多個轉換函數。

2.5.5 賦值運算符的重載

C++中,相同類型的對象之間可以直接相互賦值,但不是所有的同類型對象都可以這么操作。當對象的成員中有數組或動態的數據類型時,就不能直接相互賦值,否則在程序的編譯或執行過程中出現編譯或運行錯誤。因此,必須對賦值運算符“=”進行重載,并在重載函數中重新開辟內存空間或添加其他代碼,以保證賦值的正確性。

【例Ex_Evaluate】 賦值運算符的重載

#include <iostream.h>
#include <string.h>
class CName
{
public:
    CName (char *s)
    {
        name  =new char[strlen(s)+1];     strcpy(name,s);
    }
    ~CName ()
    {
        if (name)
        {
            delete[]name;  name=NULL;
        }
    }
    void print()
    {
        cout<< name <<endl;
    }
    C Name&operator=(CName&a)    // 賦值運算符重載
    {
        if (name)
        {
            delete[]name;  name=NULL;
        }
        if (a.name)
        {
            name = new char[strlen(a.name) + 1];
            strcpy(name, a.name);
        }
        return *this;
    }
private:
    char *name;
};
int  main()
{
    CName d1("Key"), d2("Mouse");
    d1.print();
    d1 = d2;
    d1.print();
    return 0;
}

程序運行結果如下:

Key

Mouse

需要說明的是:

(1)賦值運算符重載函數operator = ()的返回類型是CName&,注意它返回的是類的引用而不是對象。這是因為,C++要求賦值表達式左邊的表達式是左值,它能進行諸如下列的運算:

int x,y=5;                    //y是左值
(x=y)++;                      //x是左值

由于引用的實質就是對象的內存空間,所以通過引用可以改變對象的值。而如果返回的類型僅是類的對象,則操作的是對象的值而不是對象的內存空間,因此賦值操作后,不能再作為左值,從而導致程序運行終止。

(2)賦值運算符不能重載為友元函數,只能重載為一個非靜態成員函數。

(3)賦值運算符重載函數是唯一的一個不能被繼承的運算符函數。

2.5.6 自增自減運算符的重載

自增“++”和自減“--”運算符是單目運算符,它們又有前綴和后綴運算符兩種。為了區分這兩種運算符,在重載時應將后綴運算符視為雙目運算符。即

obj++或obj--

應被看做:

obj++0或obj--0

又由于這里前綴“++”中的obj必須是一個左值,因此運算符重載函數的返回類型應是引用而不能是對象。

設類為X,當用成員函數方法來實現前綴“++”和后綴“++”運算符的重載時,則可有下列一般格式:

X&  operator++();               // 前綴++
X  operator++(int);             // 后綴++

若用友元函數方法來實現前綴“++”和后綴“++”運算符的重載時,則可有下列格式:

friend  X&  operator++(X&);      // 前綴++
friend  X  operator++(X&,int);   // 后綴++

下面來說明前綴“++”和后綴“++”運算符的重載,對于“--”運算符也可類似進行。

【例Ex_Increment】 前綴“++”和后綴“++”運算符的重載

#include <iostream.h>
class CCounter
{
public:
    CCounter( int n = 0) { unCount = n; }
    CCounter&operator++();                     // 前綴++運算符重載聲明
    friend CCounter operator++(CCounter&one,int);// 后綴++運算符友元重載聲明
    void print()
    {
        cout<<unCount<<endl;
    }
private:
    unsigned unCount;
};
CCounter&CCounter::operator++()                // 前綴++運算符重載實現
{
    unCount++;
    return *this;
}
CCounter operator++(CCounter&one,int)           // 后綴++運算符友元重載實現
{
    CCounter temp = one;
    one.unCount++;
    return temp;
}
int  main()
{
    CCounter d1(8), d2;
    d2=d1++;     d1.print();     d2.print();
    d2=++d1;     d1.print();     d2.print();
    ++++d1;      d1.print();
    return 0;
}

程序中,類CCounter的運算符“++”重載函數分為前綴和后綴。對于前綴“++”運算符來說,它是通過成員函數重載來實現的,而對于后綴“++”運算符來說,它是通過友元函數來實現的。

程序運行結果如下:

9

8

10

10

12

需要說明的是:

(1)在友元重載的后綴“++”運算符函數中,由于函數中的形參僅用來在格式上區分前綴“++”運算符,本身并不使用,因此不必為形參指定形參名。

(2)在后綴“++”運算符友元重載實現中,先定義一個臨時的CCounter對象temp,其初值設為one,然后對形參one進行自增運算,最后函數返回的是temp的值而不是one值。這樣,當有:

d2 = d1++;

時,d2的值就等于友元重載函數的返回值temp,也就等于d1原來的值,滿足了后綴“++”運算符的本質。由于d1 本身還要自增,因此友元重載函數的形參應是引用對象,而不能是普通對象。

(3)由于前綴“++”操作后仍可以作為左值,也就是說“++++d1”是成立的。因此,當用成員函數或友元函數來實現前綴“++”運算符的重載時,返回的必須是引用對象。

主站蜘蛛池模板: 老河口市| 土默特右旗| 德阳市| 施秉县| 吉木乃县| 桂林市| 克山县| 福清市| 淮滨县| 高青县| 邵武市| 桓台县| 汾西县| 河津市| 普洱| 伽师县| 玉树县| 施甸县| 六枝特区| 定日县| 五莲县| 彰化市| 丰镇市| 康定县| 耒阳市| 壶关县| 井冈山市| 贡觉县| 靖江市| 安丘市| 彰化市| 怀安县| 东平县| 宜阳县| 成武县| 阳山县| 小金县| 平南县| 平塘县| 德昌县| 贡嘎县|