C++ 中的运算符重载允许你为自定义类型(如类或结构体)赋予与内置类型相似的操作行为,这让代码更直观和易读。下面我用一个表格汇总主要的运算符重载类型、关键特点和简单示例,然后为你提供详细的代码说明。
运算符类型 | 主要特点 | 简单示例 |
|---|---|---|
算术运算符 | 不修改操作数,通常返回新对象 |
|
复合赋值运算符 | 修改左操作数,返回左操作数的引用 |
|
关系运算符 | 通常返回 |
|
输入/输出运算符 | 通常为非成员友元函数,用于自定义类型的输入输出 |
|
下标运算符 [] | 通常提供 const 和非 const 版本,非 const 版本返回引用可修改元素 |
|
递增/递减运算符 | 区分前置(返回引用)和后置(返回旧值副本,参数列表含 int) |
|
函数调用运算符 () | 使对象能像函数一样使用,称为“仿函数” |
|
赋值运算符 = | 必须为成员函数,注意处理自赋值和深拷贝问题 |
|
下面是这些运算符重载的详细说明和代码案例。
📝 一、算术运算符重载
算术运算符(如 +, -, *, /)通常不修改操作数,而是返回一个新的对象。
#include <iostream>
class Vector {
public:
double x, y;
Vector(double x = 0, double y = 0) : x(x), y(y) {}
// 成员函数形式重载 + 运算符
Vector operator+(const Vector& other) const {
return Vector(x + other.x, y + other.y);
}
// 友元函数形式重载 - 运算符
friend Vector operator-(const Vector& a, const Vector& b);
};
// 实现友元函数 operator-
Vector operator-(const Vector& a, const Vector& b) {
return Vector(a.x - b.x, a.y - b.y);
}
int main() {
Vector v1(3.0, 4.0);
Vector v2(1.0, 2.0);
Vector sum = v1 + v2; // 调用 operator+
Vector diff = v1 - v2; // 调用 operator-
std::cout << "Sum: (" << sum.x << ", " << sum.y << ")\n";
std::cout << "Diff: (" << diff.x << ", " << diff.y << ")\n";
return 0;
}
说明:
- operator+被定义为类的成员函数,它使用 const引用参数,且本身也是 const成员函数,表示不修改当前对象。
- operator-被定义为非成员函数(友元函数),以便支持左操作数不是 Vector对象的情况(虽然此例中都是)。
- 成员函数局限:无法处理 5 + c1 这种情况(5不是 Complex对象,编译器不会尝试将 5置于 this的位置)
- 有缘函数优势:支持对称性操作。你可以轻松地重载以实现 c1 - 5和 5 - c1
友元函数:
- 友元函数的作用实现运算符的对称性(Symmetry):这是友元函数最重要的作用。对于像 +, -, *, /, ==, !=这样的双目运算符,通常期望其左右操作数可以互换(a + b和 b + a应该都能工作)。友元函数不受“左操作数必须是该类对象”的限制,可以自由定义各种类型的参数组合,从而完美支持对称操作。
- 访问私有成员:友元函数被授予了访问类的私有(private)和保护(protected)成员的权限。当运算符的实现需要直接操作这些私有数据时(例如复数的加减法需要直接访问实部和虚部),友元函数就非常有用,无需依赖公开的接口(getter/setter),有时这能带来更好的封装性和效率。
- 重载流操作符:重载输入 (>>) 和输出 (<<) 运算符时,必须使用友元函数(或非成员函数)。因为它们的左操作数是流对象 (std::istream或 std::ostream),而不是自定义类的对象。你无法去修改标准库中的流类来添加成员函数,所以只能以全局友元函数的形式重载。
📝 二、复合赋值运算符重载
复合赋值运算符(如 +=, -=, *=)会修改左操作数,并通常返回左操作数的引用,以支持链式操作。
class Vector {
// ... 其他成员同上
public:
// 复合赋值运算符 +=,成员函数形式
Vector& operator+=(const Vector& other) {
x += other.x;
y += other.y;
return *this; // 返回当前对象的引用
}
};
int main() {
Vector v1(3.0, 4.0);
Vector v2(1.0, 2.0);
v1 += v2; // v1 被修改为 (4.0, 6.0)
std::cout << "v1 after +=: (" << v1.x << ", " << v1.y << ")\n";
return 0;
}
说明:复合赋值运算符通常定义为成员函数,因为它们需要直接修改左操作数。
📝 三、关系运算符重载
关系运算符(如 ==, !=, <, >)用于比较两个对象,通常返回 bool值。
#include <iostream>
#include <string>
class Student {
public:
std::string name;
int id;
Student(std::string n, int i) : name(n), id(i) {}
// 重载 == 运算符
bool operator==(const Student& other) const {
return (id == other.id); // 假设学号相同即为同一学生
}
// 重载 != 运算符,通常复用 ==
bool operator!=(const Student& other) const {
return !(*this == other);
}
// 重载 < 运算符,按学号比较
bool operator<(const Student& other) const {
return id < other.id;
}
};
int main() {
Student s1("Alice", 1001);
Student s2("Bob", 1002);
Student s3("Charlie", 1001); // 学号与 s1 相同
std::cout << "s1 == s2: " << (s1 == s2) << "\n"; // 输出 0 (false)
std::cout << "s1 == s3: " << (s1 == s3) << "\n"; // 输出 1 (true)
std::cout << "s1 != s2: " << (s1 != s2) << "\n"; // 输出 1 (true)
std::cout << "s1 < s2: " << (s1 < s2) << "\n"; // 输出 1 (true)
return 0;
}
说明:关系运算符通常成对实现(例如实现了 ==,最好也实现 !=),并且通常定义为 const成员函数。
📝 四、输入/输出运算符重载
输入运算符 >>和输出运算符 <<通常重载为非成员函数,并声明为类的友元,以便访问私有成员。
#include <iostream>
class Point {
private:
int x, y;
public:
Point(int x = 0, int y = 0) : x(x), y(y) {}
// 声明友元函数
friend std::ostream& operator<<(std::ostream& os, const Point& p);
friend std::istream& operator>>(std::istream& is, Point& p);
};
// 重载输出运算符 <<
std::ostream& operator<<(std::ostream& os, const Point& p) {
os << "(" << p.x << ", " << p.y << ")";
return os;
}
// 重载输入运算符 >>
std::istream& operator>>(std::istream& is, Point& p) {
is >> p.x >> p.y;
return is;
}
int main() {
Point p(3, 4);
std::cout << "Point: " << p << "\n"; // 输出: Point: (3, 4)
Point q;
std::cout << "Enter a point (x y): ";
std::cin >> q;
std::cout << "You entered: " << q << "\n";
return 0;
}
说明:
- 输出运算符 <<的第一个参数是 std::ostream&(如 cout),第二个参数是要输出的对象的常量引用,返回 ostream引用以支持链式输出。
- 输入运算符 >>的第一个参数是 std::istream&(如 cin),第二个参数是要输入的对象引用(非常量),返回 istream引用以支持链式输入。
📝 五、下标运算符 [] 重载
下标运算符 []通常用于容器类,需要重载 const 和非 const 两个版本。
#include <iostream>
#include <stdexcept> // 用于 std::out_of_range
class SimpleVector {
private:
int* data;
size_t size;
public:
SimpleVector(size_t s) : size(s) {
data = new int[size];
for (size_t i = 0; i < size; ++i) {
data[i] = 0;
}
}
~SimpleVector() {
delete[] data;
}
// 非 const 版本,返回引用,允许修改元素
int& operator[](size_t index) {
if (index >= size) {
throw std::out_of_range("Index out of bounds");
}
return data[index];
}
// const 版本,返回常量引用,只读访问
const int& operator[](size_t index) const {
if (index >= size) {
throw std::out_of_range("Index out of bounds");
}
return data[index];
}
};
int main() {
SimpleVector vec(5);
vec[0] = 10; // 调用非 const 版本 []
vec[1] = 20;
const SimpleVector constVec(5);
// constVec[0] = 5; // 错误:const 对象调用 const 版本 [],返回常量引用,不可修改
std::cout << "vec[0]: " << vec[0] << "\n"; // 输出 10
return 0;
}
说明:非 const 版本返回元素的引用,允许对元素进行赋值修改;const 版本返回常量引用,确保不会修改对象状态。
📝 六、递增/递减运算符重载
递增 ++和递减 --运算符有前置和后置两种形式,需要区分重载。
#include <iostream>
class Counter {
private:
int count;
public:
Counter(int c = 0) : count(c) {}
// 前置递增 ++i,返回引用
Counter& operator++() {
++count;
return *this;
}
// 后置递增 i++,int 参数是占位符用于区分,返回旧值
Counter operator++(int) {
Counter temp = *this; // 保存旧值
++(*this); // 调用前置递增
return temp; // 返回旧值
}
// 类似地,可以重载前置递减和后置递减
void display() const {
std::cout << "Count: " << count << "\n";
}
};
int main() {
Counter c(5);
Counter pre = ++c; // 前置递增
c.display(); // 输出 Count: 6
pre.display(); // 输出 Count: 6
Counter post = c++; // 后置递增
c.display(); // 输出 Count: 7
post.display(); // 输出 Count: 6 (返回的是递增前的值)
return 0;
}
说明:
- 前置递增:不接受参数(或理解为隐含的 this),返回递增后对象的引用。
- 后置递增:接受一个 int类型的占位参数以区分前置版本,返回递增前对象的副本(值)。
📝 七、函数调用运算符 () 重载
重载函数调用运算符 ()的类称为函数对象或仿函数。
#include <iostream>
#include <string>
class Greeter {
public:
// 重载 () 运算符
void operator()(const std::string& name) const {
std::cout << "Hello, " << name << "!\n";
}
// 可以重载多个版本的 ()
int operator()(int a, int b) const {
return a + b;
}
};
int main() {
Greeter greet;
greet("Alice"); // 像函数一样使用对象,输出: Hello, Alice!
int sum = greet(3, 4); // 调用另一个重载版本
std::cout << "Sum: " << sum << "\n"; // 输出: Sum: 7
// 也可以匿名对象调用
Greeter()("World"); // 输出: Hello, World!
return 0;
}
说明:函数对象可以拥有状态,比普通函数更灵活,常用于 STL 算法中。
📝 八、赋值运算符 = 重载
赋值运算符 =必须重载为成员函数。如果类管理动态资源,需实现深拷贝。
#include <iostream>
#include <cstring> // 用于 strcpy
class MyString {
private:
char* data;
size_t length;
public:
// 构造函数
MyString(const char* str = "") {
length = std::strlen(str);
data = new char[length + 1];
std::strcpy(data, str);
}
// 析构函数
~MyString() {
delete[] data;
}
// 拷贝构造函数(深拷贝)
MyString(const MyString& other) {
length = other.length;
data = new char[length + 1];
std::strcpy(data, other.data);
}
// 赋值运算符重载 (深拷贝)
MyString& operator=(const MyString& other) {
// 1. 检查自赋值
if (this == &other) {
return *this;
}
// 2. 释放原有资源
delete[] data;
// 3. 分配新资源并复制数据
length = other.length;
data = new char[length + 1];
std::strcpy(data, other.data);
// 4. 返回当前对象的引用
return *this;
}
const char* getCStr() const { return data; }
};
int main() {
MyString str1("Hello");
MyString str2("World");
str2 = str1; // 调用赋值运算符
std::cout << "str2: " << str2.getCStr() << "\n"; // 输出: str2: Hello
return 0;
}
说明:
- 赋值运算符通常返回当前对象的引用(*this),以支持链式赋值(如 a = b = c)。
- 关键点:必须处理自赋值(如 a = a)情况,否则释放资源后再访问会导致未定义行为。
💡 运算符重载的注意事项
- 保持直觉性:重载的运算符行为应符合用户对该运算符的直觉预期。例如,+应该实现加法,而不是减法。
- 选择成员函数还是非成员函数:
- 赋值 =、下标 []、函数调用 ()、成员访问 ->必须是成员函数。
- 复合赋值运算符(如 +=)通常也是成员函数。
- 具有对称性的运算符(如算术运算符 +、关系运算符 ==)和输入输出运算符(<<、>>)通常是非成员函数(常声明为友元)。
- 成对重载:某些运算符通常需要成对实现,例如重载了 ==,最好也重载 !=;重载了 <,最好也重载 >、<=、>=。
- 谨慎使用:不要过度滥用运算符重载,导致代码可读性降低。确保重载的行为清晰明了。
合理地重载运算符可以极大提高代码的可读性和易用性,让你的自定义类型用起来就像内置类型一样自然。

被折叠的 条评论
为什么被折叠?



