运算符重载¶
成员函数运算符重载¶
在用户自定义类中,我们可以像定义函数一样定义运算符,也可以像函数重载一样对运算符进行重载操作。
对于在前面章节中定义的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 时,比较逻辑不必挪走,封装边界更清晰。
友元的其他形式¶
-
友元函数:在类内声明(可内联定义),或类内声明、类外定义。
-
友元类:整个类都能访问你的 private(例如迭代器访问容器内部)。
-
友元成员函数:另一个类的某个成员函数是友元。
类内声明、类外定义示例:
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;
}
约定俗成的规则¶
-
保持代码的易读性
-
重载后的运算符定义应当与对应的算术运算具有一定的相似性
-
当操作意义不明确时,不妨为其起一个合适的名称