运算符重载

内容简介

运算符重载允许自定义类型像内置类型一样使用运算符。例如让 Point + Point 表示坐标相加,让 std::cout << obj 输出对象内容。它的目标是提升表达力,不是炫技。

用法

可重载和不可重载

常见可重载运算符:

  • 算术:+ - * / %
  • 比较:== != < > <= >=
  • 赋值:= += -= *= /=
  • 下标:[]
  • 函数调用:()
  • 流输出:<<
  • 自增自减:++ --
  • 内存:new delete new[] delete[]

不可重载:

  • ..*::sizeof?:#

算术运算符

class Point {
public:
    Point(int x, int y) : x_(x), y_(y) {}
 
    Point operator+(const Point& other) const
    {
        return Point(x_ + other.x_, y_ + other.y_);
    }
 
private:
    int x_ = 0;
    int y_ = 0;
};

比较运算符

class Point {
public:
    bool operator==(const Point& other) const
    {
        return x_ == other.x_ && y_ == other.y_;
    }
 
private:
    int x_ = 0;
    int y_ = 0;
};

流输出运算符

class Point {
public:
    Point(int x, int y) : x_(x), y_(y) {}
 
    friend std::ostream& operator<<(std::ostream& os, const Point& point)
    {
        os << "(" << point.x_ << ", " << point.y_ << ")";
        return os;
    }
 
private:
    int x_ = 0;
    int y_ = 0;
};

返回 std::ostream& 是为了支持链式输出:

std::cout << point << std::endl;

下标运算符

class Buffer {
public:
    int& operator[](std::size_t index)
    {
        return data_[index];
    }
 
    const int& operator[](std::size_t index) const
    {
        return data_[index];
    }
 
private:
    std::vector<int> data_;
};

函数调用运算符

class GreaterThan {
public:
    explicit GreaterThan(int base) : base_(base) {}
 
    bool operator()(int value) const
    {
        return value > base_;
    }
 
private:
    int base_ = 0;
};

自增运算符

class Counter {
public:
    Counter& operator++()
    {
        ++value_;
        return *this;
    }
 
    Counter operator++(int)
    {
        Counter old = *this;
        ++(*this);
        return old;
    }
 
private:
    int value_ = 0;
};

最佳代码实践

  • 运算符语义要符合直觉,+ 就应该像加法,== 就应该表示等价。
  • 不修改对象的运算符成员函数加 const
  • 参数优先用 const T&,返回值按语义选择值或引用。
  • operator<< 通常写成非成员函数,必要时声明为 friend
  • 实现了 operator==,通常也要考虑 operator!=;C++20 后可用默认比较简化。
  • 有资源所有权的类要遵守“三/五/零法则”,不要只重载赋值却忘记析构和拷贝构造。

错误用法

std::ostream operator<<(std::ostream os, const Point& point);  // 错误:流对象不能按值拷贝
Counter& operator++(int)
{
    Counter old = *this;
    ++value_;
    return old;  // 错误:返回局部变量引用
}
class Person {
public:
    int* age = new int(18);
};
 
Person a;
Person b = a;  // 错误风险:默认拷贝导致两个对象共享同一块堆内存

注意事项

  • 不要重载让读者意外的运算符,例如用 + 表示保存文件。
  • 后置 ++ 通常比前置 ++ 多一次旧值保存,能用前置就用前置。
  • 自定义 operator new/delete 是高级用法,普通业务代码很少需要。
  • 类型转换运算符容易造成隐式转换问题,通常配合 explicit

学习路径