13. C++基本運算符重載

基本上我們進行運算符重載時有兩種形式,類內的運算符重載和頂層函數(shù)位置的運算符重載。

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

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

operator是關鍵字,專門用于定義重載運算符的函數(shù)。我們可以將operator 運算符名稱這一部分看做函數(shù)名,對于上面的代碼,函數(shù)名就是operator+。

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

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

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


這里我們重點看幾個運算符的重載
13.1 重載輸入輸出
13.2 重載賦值
13.3 重載下標
13.4 重載函數(shù)調用
13.5 重載自增自減
13.6 重載轉型

為了介紹基本操作符的重載,我們先來看一個操作符重載的示例。
在這個例子中,我們定義了一個復數(shù)類,一個復數(shù)包含實部和虛部兩部分,我們分別用real和imag來表示復數(shù)的實部和虛部,并將這兩個變量作為復數(shù)類的成員變量,并設置為private屬性。在復數(shù)類中,我們定義了三個構造函數(shù)用于初始化復數(shù)類的對象。之后聲明了四個操作符重載函數(shù),分別重載加減乘除四種操作符。最后定義了一個打印復數(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;   //復數(shù)的實部
    double imag;   //復數(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;
}

//打印復數(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;
   
    //復數(shù)的加法
    c3 = c1 + c2;
    cout<<"c1 + c2 = ";
    c3.display();
    cout<<endl;
   
    //復數(shù)的減法
    c3 = c1 - c2;
    cout<<"c1 - c2 = ";
    c3.display();
    cout<<endl;
   
    //復數(shù)的乘法
    c3 = c1 * c2;
    cout<<"c1 * c2 = ";
    c3.display();
    cout<<endl;
   
    //復數(shù)的除法
    c3 = c1 / c2;
    cout<<"c1 / c2 = ";
    c3.display();
    cout<<endl;
   
    return 0;
}

運算符重載的注意事項
1、并不是所有的運算符都可以重載。長度運算符sizeof、條件運算符: ?、成員選擇符.和域解析運算符::不能被重載。
2、重載不能改變運算符的優(yōu)先級和結合性。
3、重載不會改變運算符的用法,原有有幾個操作數(shù)、操作數(shù)在左邊還是在右邊,這些都不會改變。

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

4、運算符重載函數(shù)不能有默認的參數(shù),否則就改變了運算符操作數(shù)的個數(shù),這顯然是錯誤的。
5、運算符重載函數(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;   //復數(shù)的實部
    double imag;   //復數(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;
}

//打印復數(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ù)的形式進行操作符重載,但是因為無法直接訪問complex類中的私有成員,故而我們在類中增添了getimag、getreal、setimag和setreal函數(shù)以操作類中的私有成員變量,如此一來實現(xiàn)這些操作符重載函數(shù)看上去就有些復雜了,不是那么直觀。除了此種方法以外,我們還可以將complex類中的私有成員real和imag聲明為public屬性,但是如此一來就有悖類的信息隱藏機制了。

進行如下更新

#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;   //復數(shù)的實部
    double imag;   //復數(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;
}

//打印復數(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;
}

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

13.1 重載輸入與輸出操作符

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

    cout.operator<<( var )

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

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

在上面函數(shù)中istream是指輸入流。因為重載操作符函數(shù)需要用到complex類的私有成員變量,為了方便,我們將這個函數(shù)聲明為complex類的友元函數(shù)。其聲明形式如下:

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

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

    complex c;
    cin>> c;

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

    operator<<(cin , c);

在重載輸入操作符時,我們采用的是引用的方式進行傳遞參數(shù)的,輸入的參數(shù)里面包含一個istream流的引用,返回值仍然為該引用,因此我們仍然可以使用輸入操作符的鏈式輸入。

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

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

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

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

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

與istream一樣,ostream用于表示輸出流,同樣為了能夠直接訪問complex類的私有成員變量,我們將其在類內部聲明為complex類的友元函數(shù),同樣該輸出操作符重載函數(shù)可以實現(xià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;   //復數(shù)的實部
    double imag;   //復數(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;
}

//打印復數(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

關于輸入輸出我們習慣用類外的友元函數(shù)重載形式。


13.2 重載賦值操作符

賦值操作符“=”可以用來將一個對象拷貝給另一個已經存在的對象。當然拷貝構造函數(shù)同樣也會有此功能,拷貝構造函數(shù)可以將一個對象拷貝給另一個新建的對象。如果我們沒有在類中顯式定義拷貝構造函數(shù),也沒有重載賦值操作符,則系統(tǒng)會為我們的類提供一個默認的拷貝構造函數(shù)和一個賦值操作符。系統(tǒng)為我們提供的默認的拷貝構造函數(shù)只是將源對象中的數(shù)據(jù)一一拷貝給目標對象,而系統(tǒng)為類提供的賦值操作符也是這樣的一種功能。

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

在前面定義復數(shù)類時我們并未定義拷貝構造函數(shù),也沒有重載過賦值操作符,但是在例子中“c2 = c1”并未有語法錯誤,并且根據(jù)函數(shù)輸出結果也可以得知可以完成我們所需要的賦值操作。這是因為系統(tǒng)默認為類提供了一個拷貝構造函數(shù)和一個賦值操作符,而數(shù)據(jù)一對一的拷貝也滿足我們復數(shù)類的需求了。

系統(tǒng)提供的默認拷貝構造函數(shù)有一定缺陷,當類中的成員變量包含指針的時候就會有問題,會導致一些意想不到的程序漏洞,此時則需要重新定義一個拷貝構造函數(shù),同樣的此時系統(tǒng)提供的賦值操作符也已經不能滿足我們的需求了,必須要進行重載。

#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語句開始看起。這個語句就會調用類中的操作符重載函數(shù),我們可以將這一語句理解為:

    arr1.operator=( arr2 );

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

當然,如果在類中并沒有包含需要動態(tài)分配內存的指針成員變量時,我們使用系統(tǒng)提供的默認拷貝構造函數(shù)和賦值操作符也就可以了,無需再自己多此一舉的重新定義和重載一遍的。


13.3 C++重載下標操作符

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

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

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

在我們訪問數(shù)組時,通過下標去訪問數(shù)組中的元素并不具有檢查邊界溢出功能,我們可以重載下標操作符使之具有相應的功能。

#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

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

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

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

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

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

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

    arr.operator[]( 5 )

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

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

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

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

此時如果沒有定義const版本的下標操作符重載函數(shù),則將會出現(xiàn)語法錯誤而無法編譯通過的。


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

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

    返回類型 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

定義了一個Array類,這個類描述的是一個二維的數(shù)組,在類中我們先定義了一個默認構造函數(shù),之后聲明了一個帶參數(shù)的構造函數(shù)“Array(int m, int n);”,所帶的這兩個參數(shù)分別是數(shù)組的兩個維度的大小。
之后聲明了一個函數(shù)調用操作符重載函數(shù)“int & operator()(int, int);”和“const int & operator()(int, int)const;”,同樣的,因為只有常成員函數(shù)才能處理常對象,故依然在類中提供兩個版本的函數(shù)調用操作符重載函數(shù)。
可以去看一下兩個函數(shù)的函數(shù)定義,在它們的函數(shù)體中,我們先是做一個越界檢測,當然對于二維數(shù)組而言,邊界是有兩個的,因此有兩次邊界檢測的。如果沒有越界則會返回對應的值。有了這兩個函數(shù)調用操作符重載函數(shù),我們就可以用A(i,j)的形式訪問二維數(shù)組中的數(shù)據(jù)了。

當我們用A(i,j)的形式訪問二維數(shù)組中的數(shù)據(jù)時,A(i,j)會調用類中的函數(shù)調用操作符重載函數(shù),此時A(i,j)可以理解為:

    A.operator()(i, j);

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


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

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

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

class stopwatch
{
public:
    stopwatch(){ min = 0; sec = 0;}
    void setzero() { min = 0; sec = 0; }
    stopwatch run();               // 運行
    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

定義了一個簡單的秒表類,該類有兩個私有成員變量min和sec,分別代表分鐘和秒鐘。在類中聲明的成員函數(shù)setzero是用于秒表清零,run函數(shù)是用于描述秒針向前進一秒的動作,之后是三個操作符重載函數(shù),前兩個分別是重載自增操作符,最后一個是重載輸出操作符。
先來看一下run函數(shù)的實現(xiàn),run函數(shù)一開始讓秒針自增,如果此時自增結果等于60了,則應該進位,分鐘加1,秒針置零。
再來看一下operator++()函數(shù)的實現(xiàn),該函數(shù)時實現(xiàn)自增的前置形式,因此直接返回run()函數(shù)運行結果即可。
對于operator++ ( int n )函數(shù),這是實現(xiàn)自增的后置形式,自增的后置形式返回值是對象本身,但是之后再次使用該對象時,該對象自增了,因此在該函數(shù)的函數(shù)體中,先將對象保存,然后調用一次run函數(shù),之后再將先前保存的對象返回,在這個函數(shù)中參數(shù)n是沒有任何意義的,它的存在只是為了區(qū)分是前置還是后置形式。
最后我們還重載了輸出操作符,以便于我們打印計時結果。

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

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


13.6 C++重載轉型操作符

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

 operator 類型名 ();

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

  • 函數(shù)沒有返回類型;
  • 雖然沒有返回類型但是函數(shù)體中必須有return語句,其返回類型是由類型名來指定的;
  • 轉型操作符重載函數(shù)只能以類的成員函數(shù)的形式進行重載,而不能以友元函數(shù)或頂層函數(shù)的形式進行重載。
#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;
}

//轉型操作符重載函數(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;
}

我們重載了一個時鐘類clock,該類中我們聲明了一個轉型操作符重載函數(shù),該函數(shù)可以將類類型的時間轉換為一個整形,轉換后的整數(shù)是軍事時間。在主函數(shù)中我們定義了一個clock類的對象c,之后將其賦給一個整形變量time,因為我們定義了轉型操作符重載函數(shù),因此這一句話并沒有出現(xiàn)語法錯誤。

轉型操作符重載可以給程序帶來一定的方便,但是建議還是謹慎使用。因為系統(tǒng)通常在需要的時候就會調用轉型操作符重載函數(shù),該函數(shù)的調用時隱式的,有時候會給程序帶來一些意想不到的問題。


13.7 C++運算符重載注意事項

* 重載后運算符的含義應該符合原有用法習慣,重載應盡量保留運算符原有的特性。
* C++ 規(guī)定,運算符重載不改變運算符的優(yōu)先級。
* 以下運算符不能被重載:.   .*   ::   ? :    sizeof。
* 重載運算符()、[]、->、或者賦值運算符=時,只能將它們重載為成員函數(shù),不能重載為全局函數(shù)。

運算符重載的實質是將運算符重載為一個函數(shù)使用,運算符可以重載為全局函數(shù)。此時函數(shù)的參數(shù)個數(shù)就是運算符的操作數(shù)個數(shù),運算符的操作數(shù)就成為函數(shù)的實參。運算符也可以重載為成員函數(shù)。此時函數(shù)的參數(shù)個數(shù)就是運算符的操作數(shù)個數(shù)減一,運算符的操作數(shù)有一個成為函數(shù)作用的對象,其余的成為函數(shù)的實參。

必要時需要重載賦值運算符=,以避免兩個對象內部的指針指向同一片存儲空間。

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

類型的名字可以作為強制類型轉換運算符,也可以被重載為類的成員函數(shù)。它能使得對象被自動轉換為某種類型。
自增、自減運算符各有兩種重載方式,用于區(qū)別前置用法和后置用法。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370