Skip to content

运算符重载

成员函数运算符重载

在用户自定义类中,我们可以像定义函数一样定义运算符,也可以像函数重载一样对运算符进行重载操作。

对于在前面章节中定义的Student类:

class Student {
public:
    Student(String name, long int id, int age);
    void setName(String name);
    String getName() const;
    long int getID() const;
    int getAge() const;

private:
    using String = std::string;
    String _name;
    long int _id;
    int _age;
};
我们可以为其定义一个<运算符以进行排序:
class Student {
public:
    Student(String name, long int id, int age);
    void setName(String name);
    String getName() const;
    long int getID() const;
    int getAge() const;
    bool operator < (const Student& rhs) const;

private:
    using String = std::string;
    String _name;
    long int _id;
    int _age;
};

bool Student::operator<(const Student& rhs) {
    return age < rhs.age;
}
在上面的实现中,由于我们处于成员函数中,默认情况下age指的就是this->age,即Student的成员变量。

在进行重载运算符操作时通常有两种情形:

  • 成员函数

    • 在用户自定义类中进行声明

    • 允许使用成员对象(this->

  • 非成员函数

    • 重载运算符定义在任何类之外

    • 对象两侧(左值(lhs)/右值(rhs))均为参数

非成员函数运算符重载

非成员函数运算符重载允许我们为自定义类型定义运算符,使得这些类型的行为类似于内置类型。非成员函数运算符重载通常用于定义二元运算符(如 +-*/ 等),这些运算符需要访问两个操作数。

friend关键字

friend(友元)用于在访问控制上开例外:被声明为友元的函数或类,可以访问另一个类的 private / protected 成员,就像被授权进入「内部」的外部代码。

默认规则

  • 类外代码不能直接访问别的类的 private 成员。
  • 成员函数运算符(如 bool operator<(const T& rhs) const)在类内,可直接用 this 和成员。
  • 非成员函数运算符写在类外,两侧操作数都是参数;若需要读 private 成员,就要么把成员设为 public,要么使用 friend

友元函数

友元函数不是类的成员函数,但被允许访问该类的 private / protected 成员。常见用途:

  • 运算符重载(尤其是需要对称、非成员的 ==<< 等);

  • 紧密协作的另一函数/类;

  • 某些测试辅助(也可用,但更常见是测 public 接口)。

与运算符重载:operator== 示例

下面写法把 非成员 operator== 声明为友元,因此可以访问 octets(若将 octets 改为 private 仍然合法):

struct IPv4Address {
  std::array<uint8_t, 4> octets{};

  friend bool operator==(const IPv4Address& a, const IPv4Address& b) {
    return a.octets == b.octets;
  }
};

IPv4Address a{{10, 0, 0, 1}};
IPv4Address b{{10, 0, 0, 1}};
if (a == b) { /* ... */ }

成员版对比:

struct IPv4Address {
  bool operator==(const IPv4Address& other) const {
    return octets == other.octets;
  }
};
写法 特点
成员 operator== 左侧一定是本类对象:a == b 等价于 a.operator==(b)
友元 operator== 对称的两个参数;便于以后扩展(如与别的类型比较)

若成员已是 public,技术上不必friend 也能在类外实现 operator==;仍用 friend 的意义是:以后把数据成员收成 private 时,比较逻辑不必挪走,封装边界更清晰。

友元的其他形式

  1. 友元函数:在类内声明(可内联定义),或类内声明、类外定义。

  2. 友元类:整个类都能访问你的 private(例如迭代器访问容器内部)。

  3. 友元成员函数:另一个类的某个成员函数是友元。

类内声明、类外定义示例:

class Foo {
  int x_{};
  friend void Dump(const Foo& f);
};

void Dump(const Foo& f) {
  std::cout << f.x_;  // 友元可访问 private
}

重要性质

  • 不传递:A 是 B 的友元,B 是 C 的友元,不代表 A 能访问 C 的 private。

  • 不继承:派生类不会自动继承基类授予的友元权限。

  • 单向:「X 是 Y 的友元」只表示 X 能看 Y,不是 Y 能看 X。

  • 慎用:友元过多会破坏封装、增加耦合;适合少量、理由明确的访问(运算符、<<、紧密协作类型)。

与非成员重载的配合

非成员运算符若需访问 private,典型模式是:

class Vector3 {
  float x, y, z;
  friend std::ostream& operator<<(std::ostream& os, const Vector3& v);
  friend Vector3 operator*(float scalar, const Vector3& v);
};
  • operator<<:左侧是 ostream不能写成 Vector3 的成员,常用友元非成员函数。

  • operator*(float, Vector3):左操作数是 float,也不能写成只有 Vector3 参与的成员形式,故用友元实现 scalar * vec,内部再委托 vec * scalar

下文 实际应用 中的 Vector3 即采用这两种友元。

实际应用

以空间向量的定义为例:

#include <iostream>
#include <cmath>
#include <random>
#include <limits>
#include <cassert>


#pragma once
class Vector3 {
public:
    // 数据成员
    float x, y, z;  // 添加成员变量声明

    // 构造方法
    Vector3();
    Vector3(float x, float y, float z);
    explicit Vector3(float scalar);

    // 运算符重载
    Vector3 operator+(const Vector3& rhs) const;
    Vector3 operator-(const Vector3& rhs) const;
    Vector3 operator*(float scalar) const;
    Vector3 operator/(float scalar) const;

    Vector3& operator+=(const Vector3& rhs);
    Vector3& operator-=(const Vector3& rhs);
    Vector3& operator*=(float scalar);
    Vector3& operator/=(float scalar);

    // 向量运算
    float dot(const Vector3& rhs) const;
    Vector3 cross(const Vector3& rhs) const;

    // 向量归一化
    Vector3 normalized() const;
    void normalize();

    // 长度计算
    float length() const;
    float lengthSquared() const;

    // 投影和反射
    Vector3 projectOnto(const Vector3& target) const;
    Vector3 reflect(const Vector3& normal) const;

    // 比较操作
    bool isZero(float epsilon = 1e-6f) const;
    bool operator==(const Vector3& rhs) const;

    // 静态方法
    static Vector3 Zero();
    static Vector3 One();
    static Vector3 UnitX();
    static Vector3 UnitY();
    static Vector3 UnitZ();
    static Vector3 Random(float min = -1.0f, float max = 1.0f);

    // 友元函数
    friend std::ostream& operator<<(std::ostream& os, const Vector3& vec);
    friend Vector3 operator*(float scalar, const Vector3& vec);

protected:
    std::string type_tag = "vector";

};
#include "vector.h"

// 构造方法
Vector3::Vector3() : x(0), y(0), z(0) {}
Vector3::Vector3(float x, float y, float z) : x(x), y(y), z(z) {}
Vector3::Vector3(float scalar) : x(scalar), y(scalar), z(scalar) {}

// 运算符重载
Vector3 Vector3::operator+(const Vector3& rhs) const {
    return Vector3(x + rhs.x, y + rhs.y, z + rhs.z);
}

Vector3 Vector3::operator-(const Vector3& rhs) const {
    return Vector3(x - rhs.x, y - rhs.y, z - rhs.z);
}

Vector3 Vector3::operator*(float scalar) const {
    return Vector3(x * scalar, y * scalar, z * scalar);
}

Vector3 Vector3::operator/(float scalar) const {
    assert(scalar != 0 && "Division by zero");
    return *this * (1.0f / scalar);
}

// 复合赋值运算符
Vector3& Vector3::operator+=(const Vector3& rhs) {
    x += rhs.x;
    y += rhs.y;
    z += rhs.z;
    return *this;
}

Vector3& Vector3::operator-=(const Vector3& rhs) {
    x -= rhs.x;
    y -= rhs.y;
    z -= rhs.z;
    return *this;
}

Vector3& Vector3::operator*=(float scalar) {
    x *= scalar;
    y *= scalar;
    z *= scalar;
    return *this;
}

Vector3& Vector3::operator/=(float scalar) {
    assert(scalar != 0 && "Division by zero");
    return *this *= (1.0f / scalar);
}

// 向量运算
float Vector3::dot(const Vector3& rhs) const {
    return x * rhs.x + y * rhs.y + z * rhs.z;
}

Vector3 Vector3::cross(const Vector3& rhs) const {
    return Vector3(
        y * rhs.z - z * rhs.y,
        z * rhs.x - x * rhs.z,
        x * rhs.y - y * rhs.x
    );
}

// 归一化方法
Vector3 Vector3::normalized() const {
    float len = length();
        if (len <= std::numeric_limits<float>::epsilon()) {
            return Vector3::Zero();
        }
        return *this / len;
}

void Vector3::normalize() {
    if (float len = length(); len > std::numeric_limits<float>::epsilon()) {
        x /= len;
        y /= len;
        z /= len;
    }
}

// 长度计算
float Vector3::length() const {
    return std::sqrt(lengthSquared());
}

float Vector3::lengthSquared() const {
    return x*x + y*y + z*z;
}

// 投影和反射
Vector3 Vector3::projectOnto(const Vector3& target) const {
    float lenSq = target.lengthSquared();
        if (lenSq < std::numeric_limits<float>::epsilon()) {
            return Vector3::Zero();
        }
        float scale = dot(target) / lenSq;
        return target * scale;
}

Vector3 Vector3::reflect(const Vector3& normal) const {
    return *this - normal * (2.0f * dot(normal));
}

// 比较操作
    bool Vector3::isZero(float epsilon) const {
        return std::abs(x) < epsilon && 
               std::abs(y) < epsilon && 
               std::abs(z) < epsilon;
}

bool Vector3::operator==(const Vector3& rhs) const {
    return std::abs(x - rhs.x) < 1e-6f &&
           std::abs(y - rhs.y) < 1e-6f &&
           std::abs(z - rhs.z) < 1e-6f;
}

// 静态方法
Vector3 Vector3::Zero() { return Vector3(0, 0, 0); }
Vector3 Vector3::One() { return Vector3(1, 1, 1); }
Vector3 Vector3::UnitX() { return Vector3(1, 0, 0); }
Vector3 Vector3::UnitY() { return Vector3(0, 1, 0); }
Vector3 Vector3::UnitZ() { return Vector3(0, 0, 1); }

Vector3 Vector3::Random(float min, float max) {
    static std::mt19937 gen(std::random_device{}());
    std::uniform_real_distribution<float> dist(min, max);
    return Vector3(dist(gen), dist(gen), dist(gen)).normalized();
}

// 友元函数实现
std::ostream& operator<<(std::ostream& os, const Vector3& vec) {
    os << "(" << vec.x << ", " << vec.y << ", " << vec.z << ")";
    return os;
}

Vector3 operator*(float scalar, const Vector3& vec) {
    return vec * scalar;
}

约定俗成的规则

  • 保持代码的易读性

  • 重载后的运算符定义应当与对应的算术运算具有一定的相似性

  • 当操作意义不明确时,不妨为其起一个合适的名称