目录
在C++中,操作符重载(Operator Overloading)是一项强大的特性,它允许开发者为自定义类型(如类或结构体)重新定义内置操作符的行为。通过重载操作符,可以使自定义类型的对象表现得像内置类型一样自然,从而提升代码的可读性和可维护性。然而,操作符重载也是一把双刃剑,如果使用不当,可能会导致代码难以理解和调试。
一、运算符重载的基本概念
1.1 什么是运算符重载?
运算符重载是 C++ 多态性的一种表现形式,它允许用户重新定义运算符(如+
、=
、<<
等)对于自定义类型的操作行为。例如,我们可以重载+
运算符,使其能够对两个自定义的Vector
类对象进行向量加法运算,就像整数相加一样直观。
1.2 为什么需要运算符重载?
- 代码自然性:使自定义类型的操作更符合直觉,例如用
a + b
代替add(a, b)
。 - 一致性:保持与内置类型相同的运算符使用习惯,降低学习成本。
- 扩展性:为复杂数据结构提供灵活的操作方式,如矩阵运算、字符串拼接等。
二、运算符重载的语法规则
2.1 定义形式
运算符重载可以通过成员函数或 非成员函数(全局函数 / 友元函数) 实现。其基本语法如下:
成员函数重载:
返回类型 operator运算符(参数列表) {
// 函数体,实现运算符逻辑
}
- 特点:第一个操作数是当前对象(通过
this
指针隐式传递),因此参数数量为运算符操作数减一。 - 适用场景:需要修改类的私有成员时,优先使用成员函数(可直接访问私有成员)。
非成员函数重载:
返回类型 operator运算符(参数1, 参数2, ...) {
// 函数体,实现运算符逻辑
}
- 特点:需要显式传递所有操作数,通常用于需要支持左操作数为非类类型的场景(如
int + MyClass
)。 - 注意:若需访问类的私有成员,需声明为
friend
友元函数。
2.2 可重载与不可重载的运算符
C++中允许重载的操作符共有47个,常见可重载操作符:
操作符类别 | 具体操作符 |
---|---|
算术运算符 | + - * / % |
关系运算符 | == != < > <= >= |
逻辑运算符 | ! && || |
位运算符 | & | ^ ~ << >> |
赋值运算符 | = += -= *= /= %= |
其他运算符 | [] () -> , new delete |
不可重载操作符:::
.
.*
?:
sizeof
typeid
等
不可重载运算符 | 说明 |
---|---|
. |
成员访问运算符,用于访问类成员,无法重载 |
.* |
成员指针访问运算符,同理不可重载 |
:: |
作用域解析运算符,用于指定作用域,不可重载 |
?: |
三目运算符,逻辑复杂,C++ 标准禁止重载 |
sizeof |
计算类型大小的运算符,属于编译期操作,不可重载 |
2.3 操作符重载实现方式
①成员函数形式
class Vector {
public:
Vector operator+(const Vector& rhs) const {
return Vector(x + rhs.x, y + rhs.y);
}
private:
double x, y;
};
②友元函数形式
class Vector {
friend Vector operator+(const Vector& lhs, const Vector& rhs);
};
Vector operator+(const Vector& lhs, const Vector& rhs) {
return Vector(lhs.x + rhs.x, lhs.y + rhs.y);
}
③两种形式的对比
特性 | 成员函数形式 | 友元函数形式 |
---|---|---|
访问私有成员 | 直接访问 | 需要声明为friend |
左操作数类型 | 必须是类对象 | 可以是任意类型 |
隐式转换 | 仅支持右操作数 | 支持左右操作数 |
必须使用形式 | = [] () ->必须成员函数 | << >>通常使用友元形式 |
三、常见运算符重载场景与实践
3.1 算术运算符重载(+ - * /
)
场景:实现自定义数值类型(如向量、矩阵)的算术运算。
示例:向量加法(成员函数重载)
#include <iostream>
using namespace std;
class Vector {
private:
double x, y;
public:
Vector(double a = 0, double b = 0) : x(a), y(b) {}
// 成员函数重载+运算符:Vector + Vector
Vector operator+(const Vector& other) const {
return Vector(x + other.x, y + other.y);
}
void print() const {
cout << "(" << x << ", " << y << ")" << endl;
}
};
int main() {
Vector v1(1, 2), v2(3, 4);
Vector v3 = v1 + v2; // 等价于 v1.operator+(v2)
v3.print(); // 输出:(4, 6)
return 0;
}
3.2 赋值运算符重载(=
)
场景:自定义深拷贝逻辑,避免默认浅拷贝导致的资源泄漏(如动态内存管理)。
示例:字符串类的深拷贝赋值
#include <cstring>
#include <iostream>
using namespace std;
class MyString {
private:
char* data;
int length;
public:
MyString(const char* str = "") {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
}
// 赋值运算符重载(深拷贝)
MyString& operator=(const MyString& other) {
if (this != &other) { // 自赋值检查
delete[] data; // 释放原有资源
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
}
return *this; // 返回*this以便链式赋值(a = b = c)
}
~MyString() { delete[] data; }
void print() const { cout << data << endl; }
};
int main() {
MyString a("Hello"), b("World");
a = b; // 调用operator=
a.print(); // 输出:World
return 0;
}
注意事项:
- 自赋值检查:避免对同一对象重复释放资源。
- 返回引用:支持链式赋值(如
a = b = c
)。 - 深拷贝逻辑:对于包含动态资源的类,必须自定义赋值运算符。
3.3 流输入输出运算符重载(<<
和>>
)
场景:自定义类型与cout
、cin
的交互,方便调试和用户输入。
规则:必须作为非成员函数重载(因为左操作数是ostream/istream
对象,无法作为类的成员)。
示例:输出向量对象
#include <iostream>
using namespace std;
class Vector {
private:
double x, y;
public:
Vector(double a = 0, double b = 0) : x(a), y(b) {}
// 友元函数重载<<运算符,允许访问私有成员
friend ostream& operator<<(ostream& os, const Vector& vec) {
os << "(" << vec.x << ", " << vec.y << ")";
return os; // 返回流对象以便链式输出(cout << a << b)
}
};
int main() {
Vector v(3, 4);
cout << "Vector: " << v << endl; // 输出:Vector: (3, 4)
return 0;
}
3.4 关系运算符重载(==
、<
等)
场景:用于自定义类型的比较(如排序、条件判断)。
示例:复数类相等判断
#include <iostream>
using namespace std;
class Complex {
private:
double real, imag;
public:
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
// 成员函数重载==运算符
bool operator==(const Complex& other) const {
return real == other.real && imag == other.imag;
}
// 非成员函数重载<运算符(左操作数可为非Complex类型,如int + Complex)
friend bool operator<(const Complex& a, const Complex& b) {
return a.real < b.real || (a.real == b.real && a.imag < b.imag);
}
};
int main() {
Complex c1(1, 2), c2(1, 2), c3(3, 4);
cout << (c1 == c2) << endl; // 输出:1(true)
cout << (c1 < c3) << endl; // 输出:1(true)
return 0;
}
3.5 递增 / 递减运算符重载(++
、--
)
区分前置与后置版本:
- 前置
++a
:先自增,再返回值,成员函数无参数。 - 后置
a++
:先返回值,再自增,成员函数通过int
哑参数区分。
示例:计数器类的递增操作
#include <iostream>
using namespace std;
class Counter {
private:
int value;
public:
Counter(int v = 0) : value(v) {}
// 前置++:++a
Counter& operator++() { // 返回引用,避免临时对象
value++;
return *this;
}
// 后置++:a++(通过int哑参数区分)
Counter operator++(int) { // 返回值,因为需要保存旧值
Counter temp = *this; // 保存旧值
value++;
return temp; // 返回旧值
}
int get() const { return value; }
// 重载 << 运算符
friend ostream& operator<<(ostream& os, const Counter& c) {
os << c.get();
return os;
}
};
int main() {
Counter c(5);
cout << "后置++: " << c++ << endl; // 输出:5(先返回旧值,再自增)
cout << "当前值: " << c.get() << endl; // 输出:6
cout << "前置++: " << ++c << endl; // 输出:7(先自增,再返回新值)
return 0;
}
四、类型转换运算符与转换函数
除了运算符重载,C++ 还支持自定义类型之间的转换,通过类型转换运算符或转换函数实现。
4.1 类型转换运算符(类内成员函数)
语法:
operator 目标类型() const {
// 返回转换后的值
}
示例:将复数转换为浮点型(取模长)
class Complex {
private:
double real, imag;
public:
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
// 转换为double类型(模长)
operator double() const {
return sqrt(real * real + imag * imag);
}
};
int main() {
Complex c(3, 4);
double len = c; // 隐式转换,调用operator double()
cout << "模长:" << len << endl; // 输出:5
return 0;
}
注意:隐式类型转换可能导致代码可读性下降,建议用显式转换(如static_cast
)或提供命名函数(如getLength()
)。
4.2 转换构造函数(单参数构造函数)
通过单参数构造函数,可将其他类型隐式转换为当前类类型。
示例:将整数转换为复数
class Complex {
private:
double real, imag;
public:
// 单参数构造函数:int -> Complex(real=参数,imag=0)
Complex(int r) : real(r), imag(0) {}
// 其他构造函数...
};
int main() {
Complex c = 5; // 隐式转换,等价于 Complex c(5)
return 0;
}
显式转换:explicit
关键字
若不希望隐式转换发生,可在构造函数前加explicit
:
explicit Complex(int r) : real(r), imag(0) {}
Complex c = 5; // 编译错误,需显式转换:Complex c(5) 或 Complex c = static_cast<Complex>(5);
五、运算符重载的最佳实践与注意事项
5.1 保持运算符原有语义
重载运算符的行为应与内置类型的逻辑一致,避免误导用户。例如:
+
应保持交换律(a + b == b + a
)。<<
应保持左结合性,且不改变流的状态(除了输出内容)。
5.2 选择成员函数或非成员函数的原则
场景 | 推荐方式 | 示例 |
---|---|---|
改变对象状态(如++a ) |
成员函数 | 前置递增运算符 |
左操作数为自定义类型 | 成员函数或友元函数 | Vector + Vector |
左操作数为非自定义类型(如int + Vector ) |
非成员函数(友元) | operator+(int, Vector) |
流运算符(<< 、>> ) |
非成员函数(友元) | ostream& operator<<(ostream&, const MyClass&) |
5.3 避免过度使用运算符重载
- 仅对必要的运算符进行重载,避免代码复杂度激增。
- 优先使用命名函数(如
add()
)实现复杂操作,运算符重载仅用于直观场景。
5.4 处理资源管理(深拷贝与移动语义)
若类包含动态资源(如指针),需同时重载:
- 赋值运算符(
operator=
) - 拷贝构造函数
- 移动构造函数(C++11+)
- 析构函数
这被称为 “Rule of Three/Five”,确保对象生命周期内资源管理的一致性。
六、综合案例:矩阵类的运算符重载
下面通过一个完整的矩阵类示例,综合运用多种运算符重载技巧:
需求:
实现矩阵加法(
+
)、乘法(*
)。支持流输出(
<<
)。支持矩阵与标量的乘法(
Matrix * double
和double * Matrix
)。实现矩阵转置(成员函数
transpose()
)。
代码实现:
#include <iostream>
#include <vector>
#include <stdexcept>
using namespace std;
class Matrix {
private:
int rows, cols;
vector<vector<double>> data;
// 检查矩阵维度是否匹配(用于加法/乘法)
void check_dimension(const Matrix& other) const {
if (rows != other.rows || cols != other.cols) {
throw invalid_argument("矩阵维度不匹配");
}
}
public:
// 构造函数:默认构造、指定行列
Matrix(int r = 0, int c = 0) : rows(r), cols(c) {
if (r > 0 && c > 0) {
data.resize(r, vector<double>(c, 0.0));
}
}
// 初始化列表构造函数(C++11+)
Matrix(initializer_list<initializer_list<double>> list) {
rows = list.size();
if (rows == 0) return;
cols = (*list.begin()).size();
data.reserve(rows);
for (const auto& row : list) {
if (row.size() != cols) {
throw invalid_argument("行列数不一致");
}
data.emplace_back(row.begin(), row.end());
}
}
// 成员函数:矩阵加法(Matrix + Matrix)
Matrix operator+(const Matrix& other) const {
check_dimension(other);
Matrix result(rows, cols);
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
result.data[i][j] = data[i][j] + other.data[i][j];
}
}
return result;
}
// 非成员函数:矩阵与标量乘法(Matrix * double)
friend Matrix operator*(const Matrix& mat, double scalar) {
Matrix result(mat.rows, mat.cols);
for (int i = 0; i < mat.rows; ++i) {
for (int j = 0; j < mat.cols; ++j) {
result.data[i][j] = mat.data[i][j] * scalar;
}
}
return result;
}
// 非成员函数:矩阵与矩阵乘法(Matrix * Matrix)
friend Matrix operator*(const Matrix& a, const Matrix& b) {
if (a.cols != b.rows) {
throw invalid_argument("矩阵维度不匹配,无法相乘");
}
Matrix result(a.rows, b.cols);
for (int i = 0; i < a.rows; ++i) {
for (int j = 0; j < b.cols; ++j) {
for (int k = 0; k < a.cols; ++k) {
result.data[i][j] += a.data[i][k] * b.data[k][j];
}
}
}
return result;
}
// 重载 << 运算符,用于输出矩阵
friend ostream& operator<<(ostream& os, const Matrix& mat) {
for (const auto& row : mat.data) {
for (double val : row) {
os << val << " ";
}
os << endl;
}
return os;
}
};
int main() {
try {
// 创建两个矩阵
Matrix a = {
{1, 2},
{3, 4}
};
Matrix b = {
{5, 6},
{7, 8}
};
// 矩阵加法
Matrix sum = a + b;
cout << "矩阵加法结果:" << endl;
cout << sum << endl;
// 矩阵与标量乘法
Matrix scalarMultiply = a * 2;
cout << "矩阵与标量乘法结果:" << endl;
cout << scalarMultiply << endl;
// 矩阵与矩阵乘法
Matrix matrixMultiply = a * b;
cout << "矩阵与矩阵乘法结果:" << endl;
cout << matrixMultiply << endl;
} catch (const invalid_argument& e) {
cerr << "错误: " << e.what() << endl;
}
return 0;
}