C++ 基礎系列——運算符重載


1. 運算符重載簡介

所謂重載,就是賦予新的含義。函數重載(Function Overloading)可以讓一個函數名有多種功能,在不同情況下進行不同的操作。同樣運算符重載(Operator Overloading)可以讓同一個運算符可以有不同的功能。

  • + 可以對 int、float、string 等不同類型數據進行操作
  • << 既是位移運算符,又可以配合 cout 向控制台輸出數據

也可以自定義運算符重載:

class Complex
{
public:
    Complex();
    Complex(double real, double imag);
    Complex operator+(const Complex &a) const;
    void display() const; 
private:
    double m_real;
    double m_imag;
};

// ...

// 實現運算符重載
Complex Complex::operator+(const Complex &A) const{
    Complex B;
    B.m_real = this->m_real + A.m_real;
    B.m_imag = this -> m_imag + A.m_imag;
    return B;
    // return Complex(this->m_real + A.m_real, this->m_imag + A.m_imag);
}

int main(){
    Complex c1(4.3, 5.8);
    Complex c2(2.7, 3.7);
    Complex c3;
    c3 = c1 + c2;   // 運算符重載
    c3.display();
    return 0;
}
運算結果
7 + 9.5i

運算符重載其實就是定義一個函數,在函數體內實現想要的功能,當用到該運算符時,編譯器會自動調用這個函數,它本質上是函數重載。

c3 = c1 + c2;
實際上通過調用成員函數 operator+(),會轉換為下面的形式:
c3 = c1.operator+(c2);

全局范圍內重載運算符

運算符重載函數不僅可以作為類的成員函數,還可以作為全局函數。

復數加法運算通過全局范圍內重載 + 實現:

class Complex{
    // ...
    friend complex operator+(const complex &A, const complex &B);
};

// 全局函數
Complex operator+(const Complex &A, const Complex &B)
{
    return Complex(A.m_real + B.m_real, A.m_imag + B.m_imag);   // 訪問了Complex 的 private 成員變量,需要聲明為友元函數
}

2. 運算符重載時要遵循的規則

  • 能夠重載的運算符

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

    長度運算符 sizeof、條件運算符: ?、成員選擇符. 和域解析運算符::不能被重載。

  • 重載不能改變運算符的優先級和結合律

  • 重載不會改變運算符的用法,即操作數個數、位置都不會改變

  • 運算符重載函數不能有默認的參數,因為這改變了運算符操作數個數

  • 運算符重載函數既可作為類成員函數,也可為全局函數,注意全局函數如何要訪問類對象的私有成員,需要聲明為類的友元

  • 箭頭運算符->、下標運算符[]、函數調用運算符()、賦值運算符 =,只能以成員函數的形式重載。

3. 重載運算符實現形式的選擇

重載運算符可以通過成員函數和全局函數(友元)來實現

轉換構造函數

Complex c1(25, 25);
Complex c2 = c1 + 15.6;
Complex c3 = 28.23 + c1;    // 要以全局函數實現重載

這幾行代碼都可以順利運行,說明 Complex 對象可以和 double 類型對象相加。其實,編譯器在檢測到 Complex 和 double(小數默認為 double 類型)相加時,會先嘗試將 double 轉換為 Complex,或者反過來將 Complex 轉換為 double(只有類型相同的數據才能進行 + 運算),如果都轉換失
敗,或者都轉換成功(產生了二義性),才報錯。實際上,上述兩行加法代碼會轉換為:

Complex c2 = operator+(c1,Complex(15.6));
Complex c3 = operator+(Complex(28.23),c1);  

Complex(double real)在作為普通構造函數的同時,還能將 double 類型轉換為 Complex 類型,集合了“構造函數”和“類型轉換”的功能,所以被稱為「轉換構造函數」。換句話說,轉換構造函數用來將其它類型(可以是 bool、int、double等基本類型,也可以是數組、指針、結構體、類等構造類型)轉換為當前類類型。

以全局函數形式重載

以全局函數的形式重載了 +、-、*、/、==、!=,這樣做是為了保證這些運算符能夠被對稱的處理。

如果將 operator+定義為成員函數,根據“+ 運算符具有左結合性”這條原則,Complex c2 = c1 + 15.6;會被轉換為下面的形式:

Complex c2 = c1.operator+(Complex(15.6));

但是對於 Complex c3 = 28.23 + c1,編譯器會嘗試轉換為不同形式:

Complex c3 = (28.23).operator+(c1);

顯然這是錯誤的。

以成員函數形式重載

以成員函數的形式重載了 +=、-=、 *=、/=。

運算符重載的初衷是給類添加新的功能,方便類的運算,它作為類的成員函數是理所應當的, 是首選的。不過類的成員函數不能對稱處理數據,運算符的第一個運算對象不會出現類型轉換(要類的對象才能調用類的成員函數)。

C++ 規定,箭頭運算符->、下標運算符[ ]、函數調用運算符( )、賦值運算符=只能以成員函數的形式重載。

4. 重載 >> 和 <<(輸入輸出運算符)詳解

C++ 標准庫已對 左移運算符 << 和 >> 右移運算符進行了承載,如果我們定義新的類型需要輸入輸出運算符去處理,需要再進行重載。

重載運算符 >>

以全局函數的形式重載>>,使它能夠讀入兩個 double 類型的數據,並分別賦值給復數的實部和虛部:

istream & operator>>(istream &in, Complex &A)   // 友元
{
    in >> A.m_real >> A.m_image;
    return in;
}

之所以返回 istream 類 對象的引用,是為了能夠連續讀取復數,讓代碼書寫更加漂亮,例如:

Complex c1,c2;
cin >> c1 >> c2;

如果不返回引用,需要一個一個讀取:

cin >> c1;
cin >> c2;

實際上,上述 >> 運算符會被轉換成如下形式:

operator<<(cin, c);

重載運算符 <<

ostream & operator<<(ostream &out, complex &A){
    out << A.m_real <<" + "<< A.m_imag <<" i ";
}

5. 重載 [] 下標運算符

下標運算符 [] 必須以成員函數的形式進行重載。

int& Array::operator[](int i){
    return m_p[i];
}

const int & Array::operator[](int i) const{
    return m_p[i];
}

當 Array 是 const 對象時,如果沒有提供 const 版本的 operator[],會報錯。

6. 重載 ++ 和 -- 自增自減運算符

class Stopwatch{
    // ... 秒表類
public:
    Stopwatch operator++();     // ++i,前置形式 
    Stopwatch operator++(int);  // i++,后置形式
    Stopwatch run();    // 運行
private:
    int m_min;  // 分鍾
    int m_sec;  // 秒鍾
};

Stopwatch Stopwatch::run(){
    ++m_sec; 
    if(m_sec == 60){
        m_min++;
        m_sec = 0;
    } 
    return *this;
}

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

Stopwatch Stopwatch::operator++(int n){
    Stopwatch s = *this;    // i++, 先返回對象,再對象自增,所以需要將對象保存
    run();
    return s;
}

函數中參數 n 是沒有任何意義的,它的存在只是為了區分是前置形式還是后置形式。

7. 重載 new 和 delete 運算符

內存管理運算符 new、new[]、delete 和 delete[] 也可以進行重載,其重載形式既可以是類的成員函數,也可以是全局函數。一般情況下,內建的內存管理運算符就夠用了,只有在需要自己管理內存時才會重載。

// 成員函數
void * className::operator new( size_t size ){ 
    //TODO:
}

void className::operator delete( void *ptr){ 
    //TODO:
}

// 全局函數
void * operator new( size_t size ){ 
    //TODO:
}

void operator delete( void *ptr){ 
    //TODO:
}

在重載 new 或 new[] 時,無論是作為成員函數還是作為全局函數,它的第一個參數必須是 size_t 類型。size_t 表示的是要分配空間的大小,對於 new[] 的重載函數而言,size_t 則表示所需要分配的所有空間的總和。

size_t 在頭文件 中被定義為 typedef unsigned int size_t;,也就是無符號整型。

當然,重載函數也可以有其他參數,但都必須有默認值,並且第一個參數的類型必須是 size_t。

如果類中沒有定義 new 和 delete 的重載函數,那么會自動調用內建的 new 和 delete 運算符。

8. 重載()(強制類型轉換運算符)

類型強制轉換運算符是單目運算符,也可以被重載,但只能重載為成員函數,不能重載為全局函數。經過適當 重載后,(類型名)對象這個對對象進行強制類型轉換的表達式就等價於對象.operator 類型名(),即變成對運算符函數的調用。

class Complex{
public:
    // ...
    operator double()
    {
        return real;
    }
private:
    double m_real;
    double m_imag;
};

int main()
{
    Complex c(1.2,3.4);
    cout << (double)c << endl;  // 1.2
    double n = 2 + c;   // 等價於 double n = 2 + c.operator double()
}

9. 運算符重載總結

注意事項:

  • 重載后運算符的含義應該符合原有用法習慣。例如重載 + 運算符,完成的功能就應該類似於做加法,在重載的+ 運算符中做減法是不合適的。此外,重載應盡量保留運算符原有的特性。
  • C++ 規定,運算符重載不改變運算符的優先級。
  • 以下運算符不能被重載:.、.*、::、? :、sizeof。
  • 重載運算符()、[]、->、或者賦值運算符=時,只能將它們重載為成員函數,不能重載為全局函數。
  • 運算符的實質是將運算符重載為一個函數,使用運算符的表達式就會被解釋為對重載函數的調用。

  • 運算符可以重載為全局函數。此時函數的參數個數就是運算符的操作數個數,運算符的操作數就成為函數的實參。(友元)

  • 運算符也可以重載為成員函數。此時函數的參數個數就是運算符的操作數個數減一,運算符的操作數有一個成為函數作用的對象,其余的成為函數的實參。

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

  • <<和>>是在 iostream 中被重載,才成為所謂的“流插入運算符”和“流提取運算符”的。

  • 類型的名字可以作為強制類型轉換運算符,也可以被重載為類的成員函數。它能使得對象被自動轉換為某種類型。

  • 自增、自減運算符各有兩種重載方式,用於區別前置用法和后置用法。

  • 運算符重載不改變運算符的優先級。重載運算符時,應該盡量保留運算符原本的特性。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM