C++ 運算符重載全面深度解析

一、運算符重載的基本概念

1.1 什麼是運算符重載?

運算符重載 是 C++ 的一項核心特性,它允許程序員為自定義類型(類)定義運算符的行為。通過重載運算符,可以使自定義類型像內置類型一樣使用標準的運算符語法,從而增強代碼的可讀性和直觀性。

1.2 運算符重載的本質

從本質上講,運算符重載是一種特殊的函數重載。運算符被重載時,編譯器將其轉換為相應的函數調用。例如,a + b可能被轉換為 a.operator+(b)operator+(a, b)

1.3 運算符重載的限制

  • 不能創建新的運算符
  • 不能改變運算符的優先級和結合性
  • 不能改變運算符的操作數個數
  • 部分運算符不能重載(::..*?:sizeoftypeid等)
  • 至少有一個操作數是用户定義類型

二、運算符重載的基本語法

2.1 成員函數形式

class Complex {
private:
    double real, imag;
public:
    // 成員函數形式的運算符重載
    Complex operator+(const Complex& other) const {
        return Complex(real + other.real, imag + other.imag);
    }
    
    // 一元運算符重載
    Complex operator-() const {
        return Complex(-real, -imag);
    }
    
    // 複合賦值運算符
    Complex& operator+=(const Complex& other) {
        real += other.real;
        imag += other.imag;
        return *this;
    }
};

2.2 非成員函數形式

class Complex {
private:
    double real, imag;
public:
    // 聲明為友元以便訪問私有成員
    friend Complex operator+(const Complex& a, const Complex& b);
    friend std::ostream& operator<<(std::ostream& os, const Complex& c);
};

// 非成員函數形式的運算符重載
Complex operator+(const Complex& a, const Complex& b) {
    return Complex(a.real + b.real, a.imag + b.imag);
}

// 流插入運算符通常重載為非成員函數
std::ostream& operator<<(std::ostream& os, const Complex& c) {
    os << c.real << " + " << c.imag << "i";
    return os;
}

三、各種運算符重載詳解

3.1 算術運算符

二元算術運算符

class Rational {
    int numerator, denominator;
public:
    // 加法
    Rational operator+(const Rational& other) const {
        return Rational(
            numerator * other.denominator + other.numerator * denominator,
            denominator * other.denominator
        ).simplify();
    }
    
    // 減法
    Rational operator-(const Rational& other) const {
        return Rational(
            numerator * other.denominator - other.numerator * denominator,
            denominator * other.denominator
        ).simplify();
    }
    
    // 乘法
    Rational operator*(const Rational& other) const {
        return Rational(
            numerator * other.numerator,
            denominator * other.denominator
        ).simplify();
    }
    
    // 除法
    Rational operator/(const Rational& other) const {
        return Rational(
            numerator * other.denominator,
            denominator * other.numerator
        ).simplify();
    }
};

一元算術運算符

class Number {
    int value;
public:
    // 一元正號(通常返回原值)
    Number operator+() const {
        return *this;
    }
    
    // 一元負號
    Number operator-() const {
        return Number(-value);
    }
    
    // 前綴遞增
    Number& operator++() {
        ++value;
        return *this;
    }
    
    // 後綴遞增
    Number operator++(int) {  // int參數用於區分前綴和後綴
        Number temp = *this;
        ++value;
        return temp;
    }
    
    // 前綴遞減
    Number& operator--() {
        --value;
        return *this;
    }
    
    // 後綴遞減
    Number operator--(int) {
        Number temp = *this;
        --value;
        return temp;
    }
};

3.2 關係運算符

class String {
    char* data;
    size_t length;
public:
    // 相等比較
    bool operator==(const String& other) const {
        if (length != other.length) return false;
        return strcmp(data, other.data) == 0;
    }
    
    // 不等比較
    bool operator!=(const String& other) const {
        return !(*this == other);
    }
    
    // 小於比較
    bool operator<(const String& other) const {
        return strcmp(data, other.data) < 0;
    }
    
    // 大於比較
    bool operator>(const String& other) const {
        return other < *this;
    }
    
    // 小於等於
    bool operator<=(const String& other) const {
        return !(*this > other);
    }
    
    // 大於等於
    bool operator>=(const String& other) const {
        return !(*this < other);
    }
    
    // 三路比較運算符(C++20)
    #if __cplusplus >= 202002L
    auto operator<=>(const String& other) const {
        return strcmp(data, other.data) <=> 0;
    }
    #endif
};

3.3 賦值運算符

class Vector {
    int* elements;
    size_t size;
public:
    // 拷貝賦值運算符
    Vector& operator=(const Vector& other) {
        if (this != &other) {  // 防止自賦值
            delete[] elements;  // 釋放舊資源
            
            size = other.size;
            elements = new int[size];
            std::copy(other.elements, other.elements + size, elements);
        }
        return *this;
    }
    
    // 移動賦值運算符(C++11)
    Vector& operator=(Vector&& other) noexcept {
        if (this != &other) {
            delete[] elements;
            
            elements = other.elements;
            size = other.size;
            
            other.elements = nullptr;
            other.size = 0;
        }
        return *this;
    }
    
    // 複合賦值運算符
    Vector& operator+=(const Vector& other) {
        if (size != other.size) {
            throw std::invalid_argument("向量大小不匹配");
        }
        for (size_t i = 0; i < size; ++i) {
            elements[i] += other.elements[i];
        }
        return *this;
    }
    
    // 下標賦值
    int& operator[](size_t index) {
        if (index >= size) {
            throw std::out_of_range("索引超出範圍");
        }
        return elements[index];
    }
    
    // 常量版本的下標運算符
    const int& operator[](size_t index) const {
        if (index >= size) {
            throw std::out_of_range("索引超出範圍");
        }
        return elements[index];
    }
};

3.4 流運算符

class Date {
    int year, month, day;
public:
    // 聲明為友元以便訪問私有成員
    friend std::ostream& operator<<(std::ostream& os, const Date& d);
    friend std::istream& operator>>(std::istream& is, Date& d);
};

// 輸出運算符
std::ostream& operator<<(std::ostream& os, const Date& d) {
    os << std::setw(4) << std::setfill('0') << d.year << '-'
       << std::setw(2) << std::setfill('0') << d.month << '-'
       << std::setw(2) << std::setfill('0') << d.day;
    return os;
}

// 輸入運算符
std::istream& operator>>(std::istream& is, Date& d) {
    char dash1, dash2;
    is >> d.year >> dash1 >> d.month >> dash2 >> d.day;
    
    if (dash1 != '-' || dash2 != '-') {
        is.setstate(std::ios::failbit);
    }
    
    return is;
}

3.5 函數調用運算符

class Adder {
    int value;
public:
    Adder(int v) : value(v) {}
    
    // 函數調用運算符 - 使對象可調用
    int operator()(int x) const {
        return value + x;
    }
    
    // 重載版本
    double operator()(double x) const {
        return value + x;
    }
    
    // 多參數版本
    int operator()(int a, int b) const {
        return value + a + b;
    }
};

// 使用示例
Adder add5(5);
int result1 = add5(10);      // 15
double result2 = add5(3.14); // 8.14
int result3 = add5(2, 3);    // 10

3.6 類型轉換運算符

class SmartBool {
    bool value;
public:
    SmartBool(bool v) : value(v) {}
    
    // 顯式類型轉換運算符(C++11)
    explicit operator bool() const {
        return value;
    }
    
    // 轉換為int
    operator int() const {
        return value ? 1 : 0;
    }
    
    // 轉換為string
    operator std::string() const {
        return value ? "true" : "false";
    }
};

// 使用示例
SmartBool sb(true);
if (sb) {  // 需要顯式轉換,因為operator bool是explicit的
    std::cout << "true" << std::endl;
}
int n = sb;  // 隱式轉換為int
std::string s = sb;  // 隱式轉換為string

3.7 成員訪問運算符

class SmartPointer {
    int* ptr;
public:
    SmartPointer(int* p = nullptr) : ptr(p) {}
    ~SmartPointer() { delete ptr; }
    
    // 解引用運算符
    int& operator*() {
        if (!ptr) throw std::runtime_error("空指針解引用");
        return *ptr;
    }
    
    const int& operator*() const {
        if (!ptr) throw std::runtime_error("空指針解引用");
        return *ptr;
    }
    
    // 成員訪問運算符
    int* operator->() {
        return ptr;
    }
    
    const int* operator->() const {
        return ptr;
    }
    
    // 下標運算符
    int& operator[](size_t index) {
        if (!ptr) throw std::runtime_error("空指針");
        return ptr[index];
    }
    
    const int& operator[](size_t index) const {
        if (!ptr) throw std::runtime_error("空指針");
        return ptr[index];
    }
};

四、特殊運算符重載

4.1 內存管理運算符

class MemoryPool {
    static const size_t POOL_SIZE = 1024;
    static char pool[POOL_SIZE];
    static size_t offset;
    
public:
    // 重載 new 運算符
    void* operator new(size_t size) {
        if (offset + size > POOL_SIZE) {
            throw std::bad_alloc();
        }
        void* ptr = pool + offset;
        offset += size;
        return ptr;
    }
    
    // 重載 delete 運算符
    void operator delete(void* ptr) noexcept {
        // 在這個簡單的內存池中,我們不實際釋放內存
    }
    
    // 重載 new[] 運算符
    void* operator new[](size_t size) {
        return operator new(size);
    }
    
    // 重載 delete[] 運算符
    void operator delete[](void* ptr) noexcept {
        operator delete(ptr);
    }
    
    // 定位 new
    void* operator new(size_t size, void* where) {
        return where;
    }
};

4.2 逗號運算符

class CommaExample {
    int value;
public:
    CommaExample(int v) : value(v) {}
    
    // 逗號運算符重載
    CommaExample operator,(const CommaExample& other) {
        // 逗號運算符返回右側操作數
        return other;
    }
    
    int getValue() const { return value; }
};

// 使用示例
CommaExample a(1), b(2), c(3);
CommaExample d = (a, b, c);  // d 的值為 3

五、運算符重載的設計原則

5.1 一致性原則

// 好的設計:保持與內置類型一致的行為
class Rational {
    int num, den;
public:
    // 算術運算符返回新對象
    Rational operator+(const Rational& other) const {
        return Rational(num * other.den + other.num * den, den * other.den);
    }
    
    // 複合賦值運算符返回引用
    Rational& operator+=(const Rational& other) {
        num = num * other.den + other.num * den;
        den *= other.den;
        return *this;
    }
    
    // 關係運算符返回bool
    bool operator==(const Rational& other) const {
        return num * other.den == other.num * den;
    }
};

5.2 對稱性原則

class Complex {
    double real, imag;
public:
    // 非成員函數實現對稱性
    friend Complex operator+(const Complex& a, const Complex& b);
    friend Complex operator+(const Complex& a, double b);
    friend Complex operator+(double a, const Complex& b);
};

// 支持 Complex + double 和 double + Complex
Complex operator+(const Complex& a, double b) {
    return Complex(a.real + b, a.imag);
}

Complex operator+(double a, const Complex& b) {
    return Complex(a + b.real, b.imag);
}

六、現代C++中的運算符重載

6.1 移動語義與運算符重載

class Matrix {
    double* data;
    size_t rows, cols;
    
public:
    // 移動賦值運算符
    Matrix& operator=(Matrix&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            rows = other.rows;
            cols = other.cols;
            other.data = nullptr;
            other.rows = other.cols = 0;
        }
        return *this;
    }
    
    // 移動加法運算符
    Matrix operator+(Matrix&& other) && {  // 右值引用限定符
        // 如果this是右值,可以重用其存儲
        for (size_t i = 0; i < rows * cols; ++i) {
            data[i] += other.data[i];
        }
        other.~Matrix();  // 顯式銷燬other
        return std::move(*this);
    }
};

6.2 三路比較運算符(C++20)

class Version {
    int major, minor, patch;
public:
    // 自動生成所有比較運算符
    auto operator<=>(const Version& other) const = default;
    
    // 或者自定義實現
    /*
    std::strong_ordering operator<=>(const Version& other) const {
        if (auto cmp = major <=> other.major; cmp != 0) return cmp;
        if (auto cmp = minor <=> other.minor; cmp != 0) return cmp;
        return patch <=> other.patch;
    }
    
    bool operator==(const Version& other) const {
        return (*this <=> other) == 0;
    }
    */
};

七、運算符重載的性能優化

7.1 返回值優化

class Matrix {
public:
    // 返回右值引用以啓用移動語義
    Matrix operator+(const Matrix& other) const & {  // 左值版本
        Matrix result = *this;
        result += other;
        return result;  // 可能會觸發NRVO
    }
    
    Matrix operator+(const Matrix& other) && {  // 右值版本
        *this += other;
        return std::move(*this);  // 移動當前對象
    }
};

7.2 表達式模板

// 表達式模板示例(簡化版)
template<typename E1, typename E2>
class MatrixSum {
    const E1& e1;
    const E2& e2;
public:
    MatrixSum(const E1& a, const E2& b) : e1(a), e2(b) {}
    
    double operator()(size_t i, size_t j) const {
        return e1(i, j) + e2(i, j);
    }
};

template<typename E1, typename E2>
MatrixSum<E1, E2> operator+(const E1& a, const E2& b) {
    return MatrixSum<E1, E2>(a, b);
}

八、運算符重載的常見陷阱

8.1 自賦值問題

class Array {
    int* data;
    size_t size;
public:
    // 錯誤的賦值運算符,沒有處理自賦值
    Array& operator=(const Array& other) {
        delete[] data;  // 如果this == &other,這裏就出問題了
        size = other.size;
        data = new int[size];
        std::copy(other.data, other.data + size, data);
        return *this;
    }
    
    // 正確的版本
    Array& operator=(const Array& other) {
        if (this != &other) {  // 檢查自賦值
            delete[] data;
            size = other.size;
            data = new int[size];
            std::copy(other.data, other.data + size, data);
        }
        return *this;
    }
};

8.2 異常安全性

class SafeArray {
    int* data;
    size_t size;
public:
    // 強異常安全的賦值運算符
    SafeArray& operator=(const SafeArray& other) {
        if (this != &other) {
            int* new_data = new int[other.size];  // 先分配新內存
            std::copy(other.data, other.data + other.size, new_data);
            
            delete[] data;  // 再釋放舊內存
            data = new_data;
            size = other.size;
        }
        return *this;
    }
};

九、運算符重載的最佳實踐

9.1 選擇成員函數還是非成員函數

// 規則:
// 1. 賦值運算符 (=, +=, -= 等) 必須是成員函數
// 2. 函數調用運算符 (()) 必須是成員函數
// 3. 下標運算符 ([]) 必須是成員函數
// 4. 成員訪問運算符 (->) 必須是成員函數
// 5. 類型轉換運算符必須是成員函數
// 6. 對稱運算符 (+, -, *, /, ==, != 等) 應該是非成員函數
// 7. 流運算符 (<<, >>) 必須是非成員函數

9.2 實現關係運算符的技巧

// 只實現 < 和 ==,其他自動推導
class Comparable {
    int value;
public:
    bool operator<(const Comparable& other) const {
        return value < other.value;
    }
    
    bool operator==(const Comparable& other) const {
        return value == other.value;
    }
    
    // 其他關係運算符可以自動推導
    bool operator!=(const Comparable& other) const { return !(*this == other); }
    bool operator>(const Comparable& other) const { return other < *this; }
    bool operator<=(const Comparable& other) const { return !(other < *this); }
    bool operator>=(const Comparable& other) const { return !(*this < other); }
};

十、高級應用示例

10.1 多維數組的下標運算符

template<typename T, size_t Rows, size_t Cols>
class Matrix2D {
    T data[Rows][Cols];
public:
    // 代理類,用於實現a[i][j]語法
    class RowProxy {
        T* row;
    public:
        RowProxy(T* r) : row(r) {}
        T& operator[](size_t col) { return row[col]; }
        const T& operator[](size_t col) const { return row[col]; }
    };
    
    RowProxy operator[](size_t row) {
        return RowProxy(data[row]);
    }
    
    const RowProxy operator[](size_t row) const {
        return RowProxy(const_cast<T*>(data[row]));
    }
    
    // C++23 支持的多維下標運算符
    #if __cplusplus >= 202302L
    T& operator[](size_t row, size_t col) {
        return data[row][col];
    }
    
    const T& operator[](size_t row, size_t col) const {
        return data[row][col];
    }
    #endif
};

10.2 智能指針的完整實現

template<typename T>
class UniquePtr {
    T* ptr;
    
    // 刪除器
    struct DefaultDeleter {
        void operator()(T* p) const {
            delete p;
        }
    };
    
public:
    // 構造函數
    explicit UniquePtr(T* p = nullptr) : ptr(p) {}
    
    // 禁止拷貝
    UniquePtr(const UniquePtr&) = delete;
    UniquePtr& operator=(const UniquePtr&) = delete;
    
    // 移動語義
    UniquePtr(UniquePtr&& other) noexcept : ptr(other.ptr) {
        other.ptr = nullptr;
    }
    
    UniquePtr& operator=(UniquePtr&& other) noexcept {
        if (this != &other) {
            reset(other.release());
        }
        return *this;
    }
    
    // 析構函數
    ~UniquePtr() {
        if (ptr) {
            DefaultDeleter()(ptr);
        }
    }
    
    // 運算符重載
    T& operator*() const { return *ptr; }
    T* operator->() const { return ptr; }
    
    // 轉換為bool
    explicit operator bool() const { return ptr != nullptr; }
    
    // 獲取原始指針
    T* get() const { return ptr; }
    
    // 釋放所有權
    T* release() {
        T* temp = ptr;
        ptr = nullptr;
        return temp;
    }
    
    // 重置指針
    void reset(T* p = nullptr) {
        if (ptr != p) {
            DefaultDeleter()(ptr);
            ptr = p;
        }
    }
};

十一、總結

11.1 運算符重載的核心原則

  1. 保持直觀性:運算符行為應該符合用户的直覺
  2. 保持一致性:與內置類型和標準庫的行為保持一致
  3. 提供完整接口:相關運算符應該一起重載
  4. 注意性能:儘量減少不必要的拷貝
  5. 保證異常安全:正確處理異常情況

11.2 現代C++最佳實踐

class ModernClass {
    std::vector<int> data;
public:
    // 使用默認的比較運算符(C++20)
    auto operator<=>(const ModernClass&) const = default;
    
    // 使用noexcept移動操作
    ModernClass& operator=(ModernClass&&) noexcept = default;
    
    // 使用引用限定符
    ModernClass& operator+=(const ModernClass&) & {
        // 只能用於左值
        return *this;
    }
    
    ModernClass operator+(const ModernClass& other) const & {
        ModernClass result = *this;
        result += other;
        return result;
    }
    
    ModernClass operator+(const ModernClass& other) && {
        *this += other;
        return std::move(*this);
    }
};

11.3 使用場景總結

  1. 數學類:向量、矩陣、複數、有理數
  2. 容器類:自定義數組、列表、映射
  3. 智能指針:自定義內存管理
  4. 迭代器:自定義遍歷行為
  5. 字符串類:自定義文本處理
  6. 日期時間類:自定義時間計算
  7. 文件流:自定義I/O操作
  8. 表達式計算:自定義語法解析

運算符重載是C++強大表達能力的體現,正確使用可以顯著提高代碼的可讀性和可用性。但同時也需要謹慎使用,避免過度設計和不直觀的行為。通過遵循最佳實踐和設計原則,可以創建出既強大又易於使用的類接口。