前置知识: C++

运算符重载

2 minIntermediate2026/6/14

运算符重载规则与最佳实践

概述

运算符重载允许为自定义型重新定义运算符的行为,使得用户定义的型可以像内置型一样使用运算符。合理使用运算符重载可以使代码更直观、更符合领域语言的惯例;滥用则会使代码难以理解。运算符重载的核心原则是:重载后的行为应与内置运算符的语义一致。

基础概念

可重载与不可重载的运算符

可重载的运算符包括:+ - * / % ^ & | ~ ! = < > <= >= += -= *= /= %= ^= &= |= << >> >>= <<= == != <=> && || ++ -- ->* -> () [] , new delete 等。

不可重载的运算符:. .* :: ?: sizeof typeid

成员函数与非成员函数

选择原则说明
必须为成员= () [] ->
必须为非成员<< >>(左操作数是流)
建议为非成员对称运算符(+ == <=>),支持左操作数隐式转换
建议为成员复合赋值(+= -=),修改自身

快速上手

二元算术运算符

class Vector2D {
    double x_, y_;
public:
    Vector2D(double x = 0, double y = 0) : x_(x), y_(y) {}

    // 复合赋值运算符(成员函数)
    Vector2D& operator+=(const Vector2D& rhs) {
        x_ += rhs.x_;
        y_ += rhs.y_;
        return *this;
    }

    // 二元运算符(非成员函数,通过 += 实现)
    friend Vector2D operator+(Vector2D lhs, const Vector2D& rhs) {
        lhs += rhs;
        return lhs;
    }

    double x() const { return x_; }
    double y() const { return y_; }
};

比较运算符(C++20 三路比较)

class Point {
    int x_, y_;
public:
    Point(int x, int y) : x_(x), y_(y) {}

    // C++20: 只需定义 <=> 运算符,编译器自动生成 < <= > >= ==
    auto operator<=>(const Point&) const = default;
};

// C++20 之前需要逐一定义
class PointOld {
    int x_, y_;
public:
    bool operator==(const PointOld& other) const {
        return x_ == other.x_ && y_ == other.y_;
    }
    bool operator<(const PointOld& other) const {
        return x_ < other.x_ || (x_ == other.x_ && y_ < other.y_);
    }
    // 还需要定义 !=, <=, >, >=
};

详细用法

下标运算符

#include <stdexcept>

class IntArray {
    int* data_;
    size_t size_;
public:
    explicit IntArray(size_t n) : data_(new int[n]()), size_(n) {}
    ~IntArray() { delete[] data_; }

    // 非常量版本:可修改元素
    int& operator[](size_t index) {
        if (index >= size_) throw std::out_of_range("索引越界");
        return data_[index];
    }

    // 常量版本:只读访问
    const int& operator[](size_t index) const {
        if (index >= size_) throw std::out_of_range("索引越界");
        return data_[index];
    }
};

IntArray arr(10);
arr[0] = 42;           // 调用非常量版本
const IntArray& carr = arr;
int val = carr[0];     // 调用常量版本

函数调用运算符

#include <cmath>

// 函数对象(Functor)
class LinearInterpolator {
    double x1_, y1_, x2_, y2_;
public:
    LinearInterpolator(double x1, double y1, double x2, double y2)
        : x1_(x1), y1_(y1), x2_(x2), y2_(y2) {}

    double operator()(double x) const {
        double t = (x - x1_) / (x2_ - x1_);
        return y1_ + t * (y2_ - y1_);
    }
};

// 使用
LinearInterpolator interp(0, 0, 10, 100);
double y = interp(5);  // 50.0

流运算符

#include <iostream>

class Fraction {
    int numerator_, denominator_;
public:
    Fraction(int n, int d) : numerator_(n), denominator_(d) {}

    // 输出运算符必须为非成员函数(左操作数是 ostream)
    friend std::ostream& operator<<(std::ostream& os, const Fraction& f) {
        os << f.numerator_ << "/" << f.denominator_;
        return os;
    }

    // 输入运算符
    friend std::istream& operator>>(std::istream& is, Fraction& f) {
        char slash;
        is >> f.numerator_ >> slash >> f.denominator_;
        return is;
    }
};

Fraction f(3, 4);
std::cout << f << std::endl;  // 输出: 3/4

自增与自减运算符

class Counter {
    int value_;
public:
    explicit Counter(int v = 0) : value_(v) {}

    // 前置 ++:返回引用,可修改
    Counter& operator++() {
        ++value_;
        return *this;
    }

    // 后置 ++:返回旧值的副本,int 参数是区分标志
    Counter operator++(int) {
        Counter temp = *this;
        ++value_;
        return temp;
    }

    // 前置 --
    Counter& operator--() {
        --value_;
        return *this;
    }

    // 后置 --
    Counter operator--(int) {
        Counter temp = *this;
        --value_;
        return temp;
    }

    int value() const { return value_; }
};

Counter c(5);
++c;         // c.value() == 6
Counter old = c++;  // old.value() == 6, c.value() == 7

型转换运算符

class SafeBool {
    bool value_;
public:
    explicit SafeBool(bool v) : value_(v) {}

    // C++11: explicit 防止隐式转换
    explicit operator bool() const { return value_; }
};

SafeBool sb(true);
if (sb) { /* 正确:显式转换上下文 */ }
// int n = sb;  // 错误:explicit 阻止隐式转换

常见场景

智能指针风格的运算符

template<typename T>
class ScopedPtr {
    T* ptr_;
public:
    explicit ScopedPtr(T* p = nullptr) : ptr_(p) {}
    ~ScopedPtr() { delete ptr_; }

    // 解引用
    T& operator*() const { return *ptr_; }

    // 箭头运算符
    T* operator->() const { return ptr_; }

    // 布尔转换
    explicit operator bool() const { return ptr_ != nullptr; }
};

ScopedPtr<Widget> p(new Widget);
p->doWork();       // 通过 -> 访问成员
(*p).doWork();     // 通过 * 解引用
if (p) { /* ... */ }  // 通过 bool 转换检查

迭代器运算符

template<typename T>
class ArrayIterator {
    T* ptr_;
public:
    explicit ArrayIterator(T* p) : ptr_(p) {}

    T& operator*() const { return *ptr_; }
    T* operator->() const { return ptr_; }

    ArrayIterator& operator++() { ++ptr_; return *this; }
    ArrayIterator operator++(int) { auto tmp = *this; ++ptr_; return tmp; }

    bool operator==(const ArrayIterator& other) const { return ptr_ == other.ptr_; }
    bool operator!=(const ArrayIterator& other) const { return ptr_ != other.ptr_; }
};

注意事项

  • 运算符重载应保持与内置运算符一致的语义,不要让 + 执行减法
  • 不要重载 &&||, 运算符,因为重载版本无法保留内置运算符的短路求值和求值顺序
  • 二元对称运算符(如 +==)应声明为非成员函数,以支持左操作数的隐式转换
  • 复合赋值运算符(如 +=)应返回 *this引用
  • 后置自增/自减应返回旧值的副本,前置版本应返回引用
  • C++20 的 <=> 运算符大幅简化了比较运算符的实现

进阶用法

C++20 三路比较运算符

#include <compare>

class Version {
    int major_, minor_, patch_;
public:
    Version(int maj, int min, int pat) : major_(maj), minor_(min), patch_(pat) {}

    // 定义三路比较,自动生成 < <= > >= == !=
    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_;
    }

    // 也可以使用 default
    // auto operator<=>(const Version&) const = default;
};

Version v1(1, 2, 3), v2(1, 2, 4);
bool less = v1 < v2;     // true
bool equal = v1 == v2;   // false

自定义字面量

#include <chrono>

// 自定义字面量运算符
constexpr std::chrono::milliseconds operator""_ms(unsigned long long value) {
    return std::chrono::milliseconds(value);
}

constexpr std::chrono::seconds operator""_s(unsigned long long value) {
    return std::chrono::seconds(value);
}

// 使用
auto timeout = 100_ms;
auto delay = 5_s;
std::this_thread::sleep_for(timeout);