13. C++基本運(yùn)算符重載

基本上我們進(jìn)行運(yùn)算符重載時(shí)有兩種形式,類內(nèi)的運(yùn)算符重載和頂層函數(shù)位置的運(yùn)算符重載弱睦。

操作符重載指的是將C++提供的操作符進(jìn)行重新定義百姓,使之滿足我們所需要的一些功能。
運(yùn)算符重載的格式為:

返回值類型 operator 運(yùn)算符名稱 (形參表列){   }

operator是關(guān)鍵字况木,專門用于定義重載運(yùn)算符的函數(shù)垒拢。我們可以將operator 運(yùn)算符名稱這一部分看做函數(shù)名,對于上面的代碼火惊,函數(shù)名就是operator+求类。

運(yùn)算符重載函數(shù)除了函數(shù)名有特定的格式,其它地方和普通函數(shù)并沒有區(qū)別屹耐。
在C++中可以重載的操作符有:

+  -  *  /  %  ^  &  |  ~  !  =  <  >  +=  -=  *=  /=  %=  ^=  &=  |= 
<<  >>  <<=  >>=  ==  !=  <=  >=  &&  ||  ++  --  ,  ->*  ->  ()  [] 
new  new[]  delete  delete[]

上述操作符中尸疆,[]操作符是下標(biāo)操作符,()操作符是函數(shù)調(diào)用操作符。自增自減操作符的前置和后置形式都可以重載寿弱。長度運(yùn)算符“sizeof”犯眠、條件運(yùn)算符“:?”成員選擇符“.”、對象選擇符“.*”和域解析操作符“::”不能被重載症革。


這里我們重點(diǎn)看幾個(gè)運(yùn)算符的重載
13.1 重載輸入輸出
13.2 重載賦值
13.3 重載下標(biāo)
13.4 重載函數(shù)調(diào)用
13.5 重載自增自減
13.6 重載轉(zhuǎn)型

為了介紹基本操作符的重載筐咧,我們先來看一個(gè)操作符重載的示例。
在這個(gè)例子中噪矛,我們定義了一個(gè)復(fù)數(shù)類量蕊,一個(gè)復(fù)數(shù)包含實(shí)部和虛部兩部分,我們分別用real和imag來表示復(fù)數(shù)的實(shí)部和虛部艇挨,并將這兩個(gè)變量作為復(fù)數(shù)類的成員變量残炮,并設(shè)置為private屬性。在復(fù)數(shù)類中缩滨,我們定義了三個(gè)構(gòu)造函數(shù)用于初始化復(fù)數(shù)類的對象势就。之后聲明了四個(gè)操作符重載函數(shù),分別重載加減乘除四種操作符楷怒。最后定義了一個(gè)打印復(fù)數(shù)的函數(shù)display蛋勺。

#include <iostream>
using namespace std;

class complex
{
public:
    complex();
    complex(double a);
    complex(double a, double b);
    complex operator+(const complex & A)const;
    complex operator-(const complex & A)const;
    complex operator*(const complex & A)const;
    complex operator/(const complex & A)const;
    void display()const;
private:
    double real;   //復(fù)數(shù)的實(shí)部
    double imag;   //復(fù)數(shù)的虛部
};

complex::complex()
{
    real = 0.0;
    imag = 0.0;
}

complex::complex(double a)
{
    real = a;
    imag = 0.0;
}

complex::complex(double a, double b)
{
    real = a;
    imag = b;
}

//打印復(fù)數(shù)
void complex::display()const
{
    cout<<real<<" + "<<imag<<" i ";
}

//重載加法操作符
complex complex::operator+(const complex & A)const
{
    complex B;
    B.real = real + A.real;
    B.imag = imag + A.imag;
    return B;
}

//重載減法操作符
complex complex::operator-(const complex & A)const
{
    complex B;
    B.real = real - A.real;
    B.imag = imag - A.imag;
    return B;
}

//重載乘法操作符
complex complex::operator*(const complex & A)const
{
    complex B;
    B.real = real * A.real - imag * A.imag;
    B.imag = imag * A.real + real * A.imag;
    return B;
}

//重載除法操作符
complex complex::operator/(const complex & A)const
{
    complex B;
    double square = A.real * A.real + A.imag * A.imag;
    B.real = (real * A.real + imag * A.imag)/square;
    B.imag = (imag * A.real - real * A.imag)/square;
    return B;
}

int main()
{
    complex c1(4.3, -5.8);
    complex c2(8.4, 6.7);
    complex c3;
   
    //復(fù)數(shù)的加法
    c3 = c1 + c2;
    cout<<"c1 + c2 = ";
    c3.display();
    cout<<endl;
   
    //復(fù)數(shù)的減法
    c3 = c1 - c2;
    cout<<"c1 - c2 = ";
    c3.display();
    cout<<endl;
   
    //復(fù)數(shù)的乘法
    c3 = c1 * c2;
    cout<<"c1 * c2 = ";
    c3.display();
    cout<<endl;
   
    //復(fù)數(shù)的除法
    c3 = c1 / c2;
    cout<<"c1 / c2 = ";
    c3.display();
    cout<<endl;
   
    return 0;
}

運(yùn)算符重載的注意事項(xiàng)
1瓦灶、并不是所有的運(yùn)算符都可以重載鸠删。長度運(yùn)算符sizeof、條件運(yùn)算符: ?贼陶、成員選擇符.和域解析運(yùn)算符::不能被重載刃泡。
2、重載不能改變運(yùn)算符的優(yōu)先級和結(jié)合性碉怔。
3烘贴、重載不會(huì)改變運(yùn)算符的用法,原有有幾個(gè)操作數(shù)撮胧、操作數(shù)在左邊還是在右邊桨踪,這些都不會(huì)改變。

  • 重載為類成員函數(shù)時(shí):形參個(gè)數(shù)=原操作數(shù)個(gè)數(shù)- 1(后置++芹啥、--除外)
  • 這一規(guī)則限制了重載函數(shù)寫成靜態(tài)形式锻离。
  • 重載為友元函數(shù)時(shí):形參個(gè)數(shù)=原操作數(shù)個(gè)數(shù)(后置++、--除外)

4墓怀、運(yùn)算符重載函數(shù)不能有默認(rèn)的參數(shù)汽纠,否則就改變了運(yùn)算符操作數(shù)的個(gè)數(shù),這顯然是錯(cuò)誤的傀履。
5虱朵、運(yùn)算符重載函數(shù)既可以作為類的成員函數(shù),也可以作為全局函數(shù)。


頂層函數(shù)重載操作符

#include <iostream>
using namespace std;

class complex
{
public:
    complex();
    complex(double a);
    complex(double a, double b);
    double getreal() const { return real; }
    double getimag() const { return imag; }
    void setreal(double a){ real = a; }
    void setimag(double b){ imag = b; }
    void display()const;
private:
    double real;   //復(fù)數(shù)的實(shí)部
    double imag;   //復(fù)數(shù)的虛部
};

complex::complex()
{
    real = 0.0;
    imag = 0.0;
}

complex::complex(double a)
{
    real = a;
    imag = 0.0;
}

complex::complex(double a, double b)
{
    real = a;
    imag = b;
}

//打印復(fù)數(shù)
void complex::display()const
{
    cout<<real<<" + "<<imag<<" i ";
}

//重載加法操作符
complex operator+(const complex & A, const complex &B)
{
    complex C;
    C.setreal(A.getreal() + B.getreal());
    C.setimag(A.getimag() + B.getimag());
    return C;
}

//重載減法操作符
complex operator-(const complex & A, const complex &B)
{
    complex C;
    C.setreal(A.getreal() - B.getreal());
    C.setimag(A.getimag() - B.getimag());
    return C;
}

//重載乘法操作符
complex operator*(const complex & A, const complex &B)
{
    complex C;
    C.setreal(A.getreal() * B.getreal() - A.getimag() * B.getimag() );
    C.setimag(A.getimag() * B.getreal() + A.getreal() * B.getimag() );
    return C;
}

//重載除法操作符
complex operator/(const complex & A, const complex & B)
{
    complex C;
    double square = A.getreal() * A.getreal() + A.getimag() * A.getimag();
    C.setreal((A.getreal() * B.getreal() + A.getimag() * B.getimag())/square);
    C.setimag((A.getimag() * B.getreal() - A.getreal() * B.getimag())/square);
    return C;
}

int main()
{
    complex c1(4.3, -5.8);
    complex c2(8.4, 6.7);
    complex c3;
   
    c3 = c1 + c2;
    cout<<"c1 + c2 = ";
    c3.display();
    cout<<endl;

    c3 = c1 - c2;
    cout<<"c1 - c2 = ";
    c3.display();
    cout<<endl;

    c3 = c1 * c2;
    cout<<"c1 * c2 = ";
    c3.display();
    cout<<endl;

    c3 = c1 / c2;
    cout<<"c1 / c2 = ";
    c3.display();
    cout<<endl;

    return 0;
}

頂層函數(shù)的形式進(jìn)行操作符重載,但是因?yàn)闊o法直接訪問complex類中的私有成員,故而我們在類中增添了getimag奴曙、getreal仰泻、setimag和setreal函數(shù)以操作類中的私有成員變量,如此一來實(shí)現(xiàn)這些操作符重載函數(shù)看上去就有些復(fù)雜了堤结,不是那么直觀。除了此種方法以外,我們還可以將complex類中的私有成員real和imag聲明為public屬性治专,但是如此一來就有悖類的信息隱藏機(jī)制了。

進(jìn)行如下更新

#include <iostream>
using namespace std;

class complex
{
public:
    complex();
    complex(double a);
    complex(double a, double b);
    friend complex operator+(const complex & A, const complex & B);
    friend complex operator-(const complex & A, const complex & B);
    friend complex operator*(const complex & A, const complex & B);
    friend complex operator/(const complex & A, const complex & B);
    void display()const;
private:
    double real;   //復(fù)數(shù)的實(shí)部
    double imag;   //復(fù)數(shù)的虛部
};

complex::complex()
{
    real = 0.0;
    imag = 0.0;
}

complex::complex(double a)
{
    real = a;
    imag = 0.0;
}

complex::complex(double a, double b)
{
    real = a;
    imag = b;
}

//打印復(fù)數(shù)
void complex::display()const
{
    cout<<real<<" + "<<imag<<" i ";
}

//重載加法操作符
complex operator+(const complex & A, const complex &B)
{
    complex C;
    C.real = A.real + B.real;
    C.imag = A.imag + B.imag;
    return C;
}

//重載減法操作符
complex operator-(const complex & A, const complex &B)
{
    complex C;
    C.real = A.real - B.real;
    C.imag = A.imag - B.imag;
    return C;
}

//重載乘法操作符
complex operator*(const complex & A, const complex &B)
{
    complex C;
    C.real = A.real * B.real - A.imag * B.imag;
    C.imag = A.imag * B.real + A.real * B.imag;
    return C;
}

//重載除法操作符
complex operator/(const complex & A, const complex & B)
{
    complex C;
    double square = A.real * A.real + A.imag * A.imag;
    C.real = (A.real * B.real + A.imag * B.imag)/square;
    C.imag = (A.imag * B.real - A.real * B.imag)/square;
    return C;
}

int main()
{
    complex c1(4.3, -5.8);
    complex c2(8.4, 6.7);
    complex c3;
   
    c3 = c1 + c2;
    cout<<"c1 + c2 = ";
    c3.display();
    cout<<endl;

    c3 = c1 - c2;
    cout<<"c1 - c2 = ";
    c3.display();
    cout<<endl;

    c3 = c1 * c2;
    cout<<"c1 * c2 = ";
    c3.display();
    cout<<endl;

    c3 = c1 / c2;
    cout<<"c1 / c2 = ";
    c3.display();
    cout<<endl;

    return 0;
}

如此實(shí)現(xiàn)既能繼承操作符重載函數(shù)是頂層函數(shù)的優(yōu)勢遭顶,同時(shí)又能夠使操作符重載函數(shù)實(shí)現(xiàn)起來更簡單张峰。

13.1 重載輸入與輸出操作符

在C++中,系統(tǒng)已經(jīng)對左移操作符“<<”和右移操作符“>>”分別進(jìn)行了重載棒旗,使其能夠用作輸入輸出操作符喘批,但是輸入輸出的處理對象只是系統(tǒng)內(nèi)建的數(shù)據(jù)類型。系統(tǒng)重載這兩個(gè)操作符是以系統(tǒng)類成員函數(shù)的形式進(jìn)行的铣揉,因此cout<< var語句可以理解為:

    cout.operator<<( var )

頂層函數(shù)的形式來實(shí)現(xiàn)輸入操作符的重載饶深。

istream & operator>>(istream & in, complex & A)
{
    in >> A.real >> A.imag;
    return in;
}

在上面函數(shù)中istream是指輸入流。因?yàn)橹剌d操作符函數(shù)需要用到complex類的私有成員變量逛拱,為了方便敌厘,我們將這個(gè)函數(shù)聲明為complex類的友元函數(shù)。其聲明形式如下:

    friend istream & operator>>(istream & in , complex & a);

該函數(shù)可以按照如下方式使用:

    complex c;
    cin>> c;

輸入兩個(gè)數(shù)據(jù)就分別成立復(fù)數(shù)類對象c的實(shí)部和虛部了朽合【懔剑“cin>> c;”這一語句其實(shí)可以理解為:

    operator<<(cin , c);

在重載輸入操作符時(shí),我們采用的是引用的方式進(jìn)行傳遞參數(shù)的曹步,輸入的參數(shù)里面包含一個(gè)istream流的引用宪彩,返回值仍然為該引用,因此我們?nèi)匀豢梢允褂幂斎氩僮鞣逆準(zhǔn)捷斎搿?/p>

    complex c1, c2;
    cin>> c1 >> c2;

同樣的讲婚,我們也可以將輸出操作符進(jìn)行重載尿孔,使之能夠輸出復(fù)數(shù)。函數(shù)在類內(nèi)部的聲明如下:

    friend ostream &(ostream & out, complex & A);

頂層函數(shù)的實(shí)現(xiàn)如下:

ostream & operator<<(ostream & out, complex & A)
{
    out << A.real <<" + "<< A.imag <<" i ";
    return out;
}

與istream一樣筹麸,ostream用于表示輸出流活合,同樣為了能夠直接訪問complex類的私有成員變量,我們將其在類內(nèi)部聲明為complex類的友元函數(shù)竹捉,同樣該輸出操作符重載函數(shù)可以實(shí)現(xiàn)鏈?zhǔn)捷敵觥?/p>

重載輸出運(yùn)算符

#include <iostream>
using namespace std;

class complex
{
public:
    complex();
    complex(double a);
    complex(double a, double b);
    friend complex operator+(const complex & A, const complex & B);
//    friend istream & operator>>(istream & in, complex & A);
//    friend ostream & operator<<(ostream & out, complex & A);
    ostream & operator<<(ostream & out);
    void display()const;
private:
    double real;   //復(fù)數(shù)的實(shí)部
    double imag;   //復(fù)數(shù)的虛部
};

complex::complex()
{
    real = 0.0;
    imag = 0.0;
}

complex::complex(double a)
{
    real = a;
    imag = 0.0;
}

complex::complex(double a, double b)
{
    real = a;
    imag = b;
}

//打印復(fù)數(shù)
void complex::display()const
{
    cout<<real<<" + "<<imag<<" i ";
}

//重載加法操作符
complex operator+(const complex & A, const complex &B)
{
    complex C;
    C.real = A.real + B.real;
    C.imag = A.imag + B.imag;
    return C;
}


////重載輸入操作符
//istream & operator>>(istream & in, complex & A)
//{
//    in >> A.real >> A.imag;
//    return in;
//}

////重載輸出操作符
//ostream & operator<<(ostream & out, complex & A)
//{
//    out << A.real <<" + "<< A.imag <<" i ";;
//    return out;
//}

//輸出的成員函數(shù)重載
ostream& complex::operator <<(ostream & out)
{
    out << real << "+" << imag << "i" <<endl;
    return out;
}

int main()
{
    complex c1(4.3, -5.8);
    complex c2(8.4, 6.7);
    complex c3;

    c3 = c1 + c2;
    //cout<<"c1 + c2 = "<<c3<<endl;
    c3 << cout;//但顯然和我們想要的輸出格式不符合

    return 0;
}

image.png

關(guān)于輸入輸出我們習(xí)慣用類外的友元函數(shù)重載形式芜辕。


13.2 重載賦值操作符

賦值操作符“=”可以用來將一個(gè)對象拷貝給另一個(gè)已經(jīng)存在的對象。當(dāng)然拷貝構(gòu)造函數(shù)同樣也會(huì)有此功能块差,拷貝構(gòu)造函數(shù)可以將一個(gè)對象拷貝給另一個(gè)新建的對象侵续。如果我們沒有在類中顯式定義拷貝構(gòu)造函數(shù)倔丈,也沒有重載賦值操作符,則系統(tǒng)會(huì)為我們的類提供一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù)和一個(gè)賦值操作符状蜗。系統(tǒng)為我們提供的默認(rèn)的拷貝構(gòu)造函數(shù)只是將源對象中的數(shù)據(jù)一一拷貝給目標(biāo)對象需五,而系統(tǒng)為類提供的賦值操作符也是這樣的一種功能。

complex c1(4.3, -5.8);
complex c2;
c2 = c1;
cout<<c1<<endl;
cout<<c2<<endl;

在前面定義復(fù)數(shù)類時(shí)我們并未定義拷貝構(gòu)造函數(shù)轧坎,也沒有重載過賦值操作符宏邮,但是在例子中“c2 = c1”并未有語法錯(cuò)誤,并且根據(jù)函數(shù)輸出結(jié)果也可以得知可以完成我們所需要的賦值操作缸血。這是因?yàn)橄到y(tǒng)默認(rèn)為類提供了一個(gè)拷貝構(gòu)造函數(shù)和一個(gè)賦值操作符蜜氨,而數(shù)據(jù)一對一的拷貝也滿足我們復(fù)數(shù)類的需求了。

系統(tǒng)提供的默認(rèn)拷貝構(gòu)造函數(shù)有一定缺陷捎泻,當(dāng)類中的成員變量包含指針的時(shí)候就會(huì)有問題飒炎,會(huì)導(dǎo)致一些意想不到的程序漏洞,此時(shí)則需要重新定義一個(gè)拷貝構(gòu)造函數(shù)笆豁,同樣的此時(shí)系統(tǒng)提供的賦值操作符也已經(jīng)不能滿足我們的需求了郎汪,必須要進(jìn)行重載。

#include<iostream>
using namespace std;

class Array
{
public:
    Array(){length = 0; num = NULL;}
    Array(int * A, int n);
    Array(Array & a);
    Array & operator= (const Array & a);
    void setnum(int value, int index);
    int * getaddress();
    void display();
    int getlength(){return length;}
private:
    int length;
    int * num;
};

Array::Array(Array & a)
{
    if(a.num != NULL)
    {
        length = a.length;
        num = new int[length];
        for(int i=0; i<length; i++)
        {
            num[i] = a.num[i];
        }
    }
    else
    {
        length = 0;
        num = 0;
    }
}

//重載賦值操作符
Array & Array::operator= (const Array & a)
{
    if( this != &a )
    {
        delete[] num;
        if(a.num != NULL)
        {
            length = a.length;
            num = new int[length];
            for(int i=0; i<length; i++)
                num[i] = a.num[i];
        }
        else
        {
            length = 0;
            num = 0;
        }
    }
    return *this;
}

Array::Array(int *A, int n)
{
    num = new int[n];
    length = n;
    for(int i=0; i<n; i++)
        num[i] = A[i];
}

void Array::setnum(int value, int index)
{
    if(index < length){
        num[index] = value;
    }
    else{
        cout<<"index out of range!"<<endl;
    }
}

void Array::display()
{
    for(int i=0; i<length; i++){
        cout<<num[i]<<" ";
    }
    cout<<endl;
}

int * Array::getaddress()
{
    return num;
}

int main()
{
    int A[5] = {1,2,3,4,5};
    Array arr1(A, 5);
    arr1.display();
    Array arr2(arr1);
    arr2.display();
    arr2.setnum(8,2);
    arr1.display();
    arr2.display();
    cout<<arr1.getaddress()<<" "<<arr2.getaddress()<<endl;
    arr1 = arr2;
    arr1.display();
    arr2.display();
    arr2.setnum(9,3);
    arr1.display();
    arr2.display();
    cout<<arr1.getaddress()<<" "<<arr2.getaddress()<<endl;
    return 0;
}

image.png

例子中我們以類成員函數(shù)的形式重載了賦值操作符闯狱,從arr1 = arr2語句開始看起煞赢。這個(gè)語句就會(huì)調(diào)用類中的操作符重載函數(shù),我們可以將這一語句理解為:

    arr1.operator=( arr2 );

然后就會(huì)執(zhí)行賦值操作符重載函數(shù)的函數(shù)體中的代碼哄孤,在該函數(shù)體中我們?yōu)閍rr1重新開辟了一個(gè)內(nèi)存空間照筑,因此就可以規(guī)避arr1和arr2中的num指向同一塊存儲(chǔ)區(qū)域的風(fēng)險(xiǎn)。如此一來使用系統(tǒng)默認(rèn)提供的賦值操作符所帶來的風(fēng)險(xiǎn)就可以避免了录豺。在這之后的語句中朦肘,我們還修改了arr2中的數(shù)據(jù)饭弓,但是這樣的修改并沒有影響到arr1双饥。

當(dāng)然,如果在類中并沒有包含需要?jiǎng)討B(tài)分配內(nèi)存的指針成員變量時(shí)弟断,我們使用系統(tǒng)提供的默認(rèn)拷貝構(gòu)造函數(shù)和賦值操作符也就可以了咏花,無需再自己多此一舉的重新定義和重載一遍的。


13.3 C++重載下標(biāo)操作符

下標(biāo)操作符是必須要以類的成員函數(shù)的形式進(jìn)行重載的阀趴。其在類中的聲明格式如下:

    返回類型 & operator[] (參數(shù))
或
    const 返回類型 & operator[] (參數(shù))

如果使用第一種聲明方式昏翰,操作符重載函數(shù)不僅可以訪問對象,同時(shí)還可以修改對象刘急。
如果使用第二種聲明方式棚菊,則操作符重載函數(shù)只能訪問而不能修改對象。

在我們訪問數(shù)組時(shí)叔汁,通過下標(biāo)去訪問數(shù)組中的元素并不具有檢查邊界溢出功能统求,我們可以重載下標(biāo)操作符使之具有相應(yīng)的功能检碗。

#include<iostream>
#include<string>
using namespace std;

class Array
{
public:
    Array(){length = 0; num = NULL;}
    Array(int n);
    int & operator[]( int );
    const int & operator[]( int )const;
    int getlength() const {return length;}
private:
    int length;
    int * num;
};

Array::Array(int n)
{
    try
    {
        num = new int[n];
    }
    catch(bad_alloc)
    {
        cerr<<"allocate storage failure!"<<endl;
        throw;
    }
    length = n;
}

int& Array::operator[](int i)
{
    if(i < 0 || i >= length){
        throw string("out of bounds");
    }
    return num[i];
}

const int & Array::operator[](int i) const
{
    if(i < 0 || i >= length){
        throw string("out of bounds");
    }
    return num[i];
}

int main()
{
    Array A(5);
    int i;
    try
    {
        for(i = 0; i < A.getlength(); i++){
            A[i] = i;
        }
        for(i = 0 ;i < 6; i++ ){
            cout<< A[i] <<endl;
        }
    }
    catch(string s)
    {
        cerr<< s <<", i = "<< i <<endl;
    }

    return 0;
}

image.png

本例中定義了一個(gè)Array類,表示的是一個(gè)整形數(shù)組码邻,在類中我們重載了下標(biāo)操作符折剃,使之具備檢測下標(biāo)溢出的功能。重載下標(biāo)操作符像屋,我們提供了兩個(gè)版本的重載下標(biāo)操作符函數(shù):

    int & operator[]( int );
    const int & operator[]( int )const;

注意:
第一個(gè)下標(biāo)操作符重載函數(shù)最后面不帶有const怕犁,加上const意味著該成員函數(shù)是常成員函數(shù),如果第一個(gè)函數(shù)后面也加上了const己莺,則兩個(gè)函數(shù)僅有返回值不相同奏甫,這個(gè)不足以用于區(qū)分函數(shù),編譯器會(huì)提示語法錯(cuò)誤凌受。

這兩種版本的下標(biāo)操作符重載函數(shù)其實(shí)很好理解扶檐,一個(gè)是可以修改類對象,下面一個(gè)則只可以訪問對象而不能修改對象胁艰。對于上面一種下標(biāo)操作符重載函數(shù)的聲明款筑,以下兩個(gè)語句都是有效的:

    arr[5] = 7;
    int var = arr[3];

換言之,我們既可以訪問類對象腾么,同時(shí)又能修改類對象奈梳。“arr[5]”其實(shí)可以理解為:

    arr.operator[]( 5 )

而對于下面一種下標(biāo)操作符重載函數(shù)解虱,我們不能修改對象攘须,也就是說語句“arr[5] = 7;”語句是無效的,但是它依然可以用于訪問對象殴泰,因此“int var = arr[3];”語句仍然有效于宙。

我們再來看一下下標(biāo)操作符重載函數(shù)的定義,在函數(shù)體內(nèi)部悍汛,先進(jìn)行下標(biāo)越界檢測捞魁,如果出現(xiàn)越界則拋出異常,否則就返回下標(biāo) i 所對應(yīng)的數(shù)據(jù)离咐。這兩種版本的下標(biāo)操作符重載函數(shù)的函數(shù)定義都是如此谱俭。

注意:
非const成員函數(shù)不能處理const對象,因此通常我們在設(shè)計(jì)程序時(shí)宵蛀,會(huì)同時(shí)提供兩種版本的操作符重載函數(shù)昆著。
display頂層函數(shù),用于打印對象數(shù)組中的所有元素

void display(const Array & A)
{
    for(int i=0; i < A.getlength(); i++)
        cout<< A[i] <<endl;
}

此時(shí)如果沒有定義const版本的下標(biāo)操作符重載函數(shù)术陶,則將會(huì)出現(xiàn)語法錯(cuò)誤而無法編譯通過的凑懂。


13.4 C++函數(shù)調(diào)用操作符重載

與下標(biāo)操作符重載函數(shù)相同,我們同樣需要以類成員函數(shù)的形式對函數(shù)調(diào)用操作符“()”進(jìn)行重載梧宫。其聲明語法只有一種形式:

    返回類型 operator()( 參數(shù)列表 );
#include<iostream>
#include<string>
using namespace std;

class Array
{
public:
    Array(){len1 = 0; len2 = 0; num = NULL; }
    Array(int m, int n);
    int & operator()(int, int);
    const int & operator()(int, int)const;
    int getlen1()const {return len1;}
    int getlen2()const {return len2;}
private:
    int len1;
    int len2;
    int * num;
};

Array::Array(int m, int n)
{
    int size = m * n;
    try
    {
        num = new int[size];
    }
    catch(bad_alloc)
    {
        cerr<<"allocate storage failure!"<<endl;
        throw;
    }
    len1 = m;
    len2 = n;
}

int & Array::operator()(int i, int j)
{
    if(i < 0 || i >= len1)
        throw string("1 out of bounds!");
    if(j < 0 || j >= len2)
        throw string("2 out of bounds!");
    return num[ i*len2 + j ];
}

const int & Array::operator()(int i, int j)const
{
    if(i < 0 || i >= len1)
        throw string("1 out of bounds!");
    if(j < 0 || j >= len2)
        throw string("2 out of bounds!");
    return num[ i*len2 + j ];
}

int main()
{
    Array A(3,4);
    int i,j;
    for(i = 0; i < A.getlen1(); i++){
        for(j = 0; j < A.getlen2(); j++){
            A(i,j) = i * A.getlen2() + j;
        }
    }
    for(i = 0; i < A.getlen1(); i++){
        for(j = 0; j < A.getlen2(); j++){
            cout<< A(i,j)<<" ";
        }
        cout<<endl;
    }
    try
    {
        cout<< A(5, 3) << endl;
    }
    catch(string s)
    {
        cerr<< s <<endl;
    }
    try
    {
        cout<< A(2, 6) << endl;
    }
    catch(string s)
    {
        cerr<< s <<endl;
    }
    return 0;
}

image.png

定義了一個(gè)Array類接谨,這個(gè)類描述的是一個(gè)二維的數(shù)組杭攻,在類中我們先定義了一個(gè)默認(rèn)構(gòu)造函數(shù),之后聲明了一個(gè)帶參數(shù)的構(gòu)造函數(shù)“Array(int m, int n);”疤坝,所帶的這兩個(gè)參數(shù)分別是數(shù)組的兩個(gè)維度的大小兆解。
之后聲明了一個(gè)函數(shù)調(diào)用操作符重載函數(shù)“int & operator()(int, int);”和“const int & operator()(int, int)const;”,同樣的跑揉,因?yàn)橹挥谐3蓡T函數(shù)才能處理常對象锅睛,故依然在類中提供兩個(gè)版本的函數(shù)調(diào)用操作符重載函數(shù)。
可以去看一下兩個(gè)函數(shù)的函數(shù)定義历谍,在它們的函數(shù)體中现拒,我們先是做一個(gè)越界檢測,當(dāng)然對于二維數(shù)組而言望侈,邊界是有兩個(gè)的印蔬,因此有兩次邊界檢測的。如果沒有越界則會(huì)返回對應(yīng)的值脱衙。有了這兩個(gè)函數(shù)調(diào)用操作符重載函數(shù)侥猬,我們就可以用A(i,j)的形式訪問二維數(shù)組中的數(shù)據(jù)了。

當(dāng)我們用A(i,j)的形式訪問二維數(shù)組中的數(shù)據(jù)時(shí)捐韩,A(i,j)會(huì)調(diào)用類中的函數(shù)調(diào)用操作符重載函數(shù)退唠,此時(shí)A(i,j)可以理解為:

    A.operator()(i, j);

主函數(shù)中異常捕獲語句,我們先運(yùn)行的是A(5, 3)荤胁,故而是第一個(gè)邊界越界了瞧预,因此先拋出“1 out of bounds!”的異常,而后又運(yùn)行A(2, 6)仅政,此時(shí)為第二個(gè)邊界越界垢油,拋出“2 out of bounds!”的異常。


13.5 C++重載自增與自減操作符

自增“++”與自減“--”都是一元操作符圆丹,其前置和后置兩種形式都可以被重載滩愁。有了前面介紹操作符重載的基礎(chǔ),我們就直接以示例的形式介紹自增與自減操作符的前置與后置重載方法运褪。

#include <iostream>
#include <iomanip>
using namespace std;

class stopwatch
{
public:
    stopwatch(){ min = 0; sec = 0;}
    void setzero() { min = 0; sec = 0; }
    stopwatch run();               // 運(yùn)行
    stopwatch operator++();        // ++ i
    stopwatch operator++(int);     // i ++
    friend ostream & operator<<( ostream &, const stopwatch &);
private:
    int min; //分鐘
    int sec; //秒鐘
};

stopwatch stopwatch::run()
{
    ++ sec;
    if( sec == 60 )
    {
        min ++;
        sec = 0;
    }
    return * this;
}

stopwatch stopwatch::operator++()
{
    return run();
}

stopwatch stopwatch::operator++(int n)
{
    stopwatch s = *this;
    run();
    return s;
}

ostream & operator<<( ostream & out, const stopwatch & s)
{
    out<< setfill('0')<< setw(2) << s.min
       << ":" <<setw(2) << s.sec;
    return out;
}

int main()
{
    stopwatch s1, s2;
    s1 = s2 ++;
    cout << " s1 "<< s1 <<endl;
    cout << " s2 "<< s2 <<endl;
    s1.setzero();
    s2.setzero();
    s1 = ++ s2;
    cout << " s1 "<< s1 <<endl;
    cout << " s2 "<< s2 <<endl;
    return 0;
}
image.png

定義了一個(gè)簡單的秒表類惊楼,該類有兩個(gè)私有成員變量min和sec玖瘸,分別代表分鐘和秒鐘秸讹。在類中聲明的成員函數(shù)setzero是用于秒表清零,run函數(shù)是用于描述秒針向前進(jìn)一秒的動(dòng)作雅倒,之后是三個(gè)操作符重載函數(shù)璃诀,前兩個(gè)分別是重載自增操作符,最后一個(gè)是重載輸出操作符蔑匣。
先來看一下run函數(shù)的實(shí)現(xiàn)劣欢,run函數(shù)一開始讓秒針自增棕诵,如果此時(shí)自增結(jié)果等于60了,則應(yīng)該進(jìn)位凿将,分鐘加1校套,秒針置零。
再來看一下operator++()函數(shù)的實(shí)現(xiàn)牧抵,該函數(shù)時(shí)實(shí)現(xiàn)自增的前置形式笛匙,因此直接返回run()函數(shù)運(yùn)行結(jié)果即可。
對于operator++ ( int n )函數(shù)犀变,這是實(shí)現(xiàn)自增的后置形式妹孙,自增的后置形式返回值是對象本身,但是之后再次使用該對象時(shí)获枝,該對象自增了蠢正,因此在該函數(shù)的函數(shù)體中,先將對象保存省店,然后調(diào)用一次run函數(shù)嚣崭,之后再將先前保存的對象返回,在這個(gè)函數(shù)中參數(shù)n是沒有任何意義的懦傍,它的存在只是為了區(qū)分是前置還是后置形式有鹿。
最后我們還重載了輸出操作符,以便于我們打印計(jì)時(shí)結(jié)果谎脯。

對照主函數(shù)來看程序運(yùn)行結(jié)果葱跋,主函數(shù)一開始我們定義了兩個(gè)對象s1和s2,第一次操作是s1 = s2 ++; 采用的是后置形式源梭,這可以理解為s1 = s2 并且s2自增娱俺,輸出結(jié)果是s1是處于置零狀態(tài),s2自增了一秒鐘废麻。之后兩個(gè)對象都清零荠卷,清零后s1 = ++ s2; 這個(gè)可以理解為s2自增并將自增結(jié)果賦給s1,如此一來兩個(gè)對象都自增一秒鐘烛愧。

自減操作符的重載跟自增操作符類似油宜,這里就不再贅述了。


13.6 C++重載轉(zhuǎn)型操作符

重載轉(zhuǎn)型操作符怜姿。轉(zhuǎn)型操作符重載函數(shù)的聲明語法如下:

 operator 類型名 ();

轉(zhuǎn)型操作符重載函數(shù)有幾點(diǎn)需要注意的:

  • 函數(shù)沒有返回類型慎冤;
  • 雖然沒有返回類型但是函數(shù)體中必須有return語句,其返回類型是由類型名來指定的沧卢;
  • 轉(zhuǎn)型操作符重載函數(shù)只能以類的成員函數(shù)的形式進(jìn)行重載蚁堤,而不能以友元函數(shù)或頂層函數(shù)的形式進(jìn)行重載。
#include <iostream>
using namespace std;

class clock
{
public:
    clock(){hour = min = ap = 0;}
    clock(int h, int m, int ap);
    operator int();
private:
    int hour;
    int min;
    int ap;  // 0表示am, 1表示pm
};

clock::clock(int h, int m, int ap)
{
    hour = h;
    min = m;
    this->ap = ap;
}

//轉(zhuǎn)型操作符重載函數(shù)
clock::operator int()
{
    int time = hour;
    if(time == 12){
        time = 0;
    }
    if(ap == 1){
        time += 12;
    }
    time *= 100;
    time += min;
    return time;
}

int main()
{
    clock c(5,7,1);
    int time = c;
    cout<<time<<endl;
    return 0;
}

我們重載了一個(gè)時(shí)鐘類clock但狭,該類中我們聲明了一個(gè)轉(zhuǎn)型操作符重載函數(shù)披诗,該函數(shù)可以將類類型的時(shí)間轉(zhuǎn)換為一個(gè)整形撬即,轉(zhuǎn)換后的整數(shù)是軍事時(shí)間。在主函數(shù)中我們定義了一個(gè)clock類的對象c呈队,之后將其賦給一個(gè)整形變量time剥槐,因?yàn)槲覀兌x了轉(zhuǎn)型操作符重載函數(shù),因此這一句話并沒有出現(xiàn)語法錯(cuò)誤宪摧。

轉(zhuǎn)型操作符重載可以給程序帶來一定的方便才沧,但是建議還是謹(jǐn)慎使用。因?yàn)橄到y(tǒng)通常在需要的時(shí)候就會(huì)調(diào)用轉(zhuǎn)型操作符重載函數(shù)绍刮,該函數(shù)的調(diào)用時(shí)隱式的温圆,有時(shí)候會(huì)給程序帶來一些意想不到的問題。


13.7 C++運(yùn)算符重載注意事項(xiàng)

* 重載后運(yùn)算符的含義應(yīng)該符合原有用法習(xí)慣孩革,重載應(yīng)盡量保留運(yùn)算符原有的特性岁歉。
* C++ 規(guī)定,運(yùn)算符重載不改變運(yùn)算符的優(yōu)先級膝蜈。
* 以下運(yùn)算符不能被重載:.   .*   ::   ? :    sizeof锅移。
* 重載運(yùn)算符()、[]饱搏、->非剃、或者賦值運(yùn)算符=時(shí),只能將它們重載為成員函數(shù)推沸,不能重載為全局函數(shù)备绽。

運(yùn)算符重載的實(shí)質(zhì)是將運(yùn)算符重載為一個(gè)函數(shù)使用,運(yùn)算符可以重載為全局函數(shù)鬓催。此時(shí)函數(shù)的參數(shù)個(gè)數(shù)就是運(yùn)算符的操作數(shù)個(gè)數(shù)肺素,運(yùn)算符的操作數(shù)就成為函數(shù)的實(shí)參。運(yùn)算符也可以重載為成員函數(shù)宇驾。此時(shí)函數(shù)的參數(shù)個(gè)數(shù)就是運(yùn)算符的操作數(shù)個(gè)數(shù)減一倍靡,運(yùn)算符的操作數(shù)有一個(gè)成為函數(shù)作用的對象,其余的成為函數(shù)的實(shí)參课舍。

必要時(shí)需要重載賦值運(yùn)算符=塌西,以避免兩個(gè)對象內(nèi)部的指針指向同一片存儲(chǔ)空間。

運(yùn)算符可以重載為全局函數(shù)筝尾,然后聲明為類的友元捡需。
<<和>>是在 iostream 中被重載,才成為所謂的“流插入運(yùn)算符”和“流提取運(yùn)算符”的忿等。

類型的名字可以作為強(qiáng)制類型轉(zhuǎn)換運(yùn)算符栖忠,也可以被重載為類的成員函數(shù)。它能使得對象被自動(dòng)轉(zhuǎn)換為某種類型贸街。
自增庵寞、自減運(yùn)算符各有兩種重載方式,用于區(qū)別前置用法和后置用法薛匪。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捐川,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子逸尖,更是在濱河造成了極大的恐慌古沥,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件娇跟,死亡現(xiàn)場離奇詭異岩齿,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)苞俘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門盹沈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人吃谣,你說我怎么就攤上這事乞封。” “怎么了岗憋?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵肃晚,是天一觀的道長。 經(jīng)常有香客問我仔戈,道長关串,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任监徘,我火速辦了婚禮悍缠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘耐量。我一直安慰自己飞蚓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布廊蜒。 她就那樣靜靜地躺著趴拧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪山叮。 梳的紋絲不亂的頭發(fā)上著榴,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機(jī)與錄音屁倔,去河邊找鬼脑又。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的问麸。 我是一名探鬼主播往衷,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼严卖!你這毒婦竟也來了席舍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤哮笆,失蹤者是張志新(化名)和其女友劉穎来颤,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體稠肘,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡福铅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了项阴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片滑黔。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鲁冯,靈堂內(nèi)的尸體忽然破棺而出拷沸,到底是詐尸還是另有隱情,我是刑警寧澤薯演,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布撞芍,位于F島的核電站,受9級特大地震影響跨扮,放射性物質(zhì)發(fā)生泄漏序无。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一衡创、第九天 我趴在偏房一處隱蔽的房頂上張望帝嗡。 院中可真熱鬧,春花似錦璃氢、人聲如沸哟玷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽巢寡。三九已至,卻和暖如春椰苟,著一層夾襖步出監(jiān)牢的瞬間抑月,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工舆蝴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谦絮,地道東北人题诵。 一個(gè)月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像层皱,于是被迫代替她去往敵國和親性锭。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355