C++ 运算符重载:+、-、*、/ 操作符
运算符重载是 C++ 中的一项强大功能,它允许你赋予自定义对象(如类或结构体)使用标准运算符(如 +、-、*、/)的能力。这使得代码更符合直觉,读起来更像自然语言。
本文将以“复数”运算为例,手把手教你如何实现这四个基本算术运算符的重载。
1. 准备基础类结构
首先,我们需要定义一个表示复数的类 Complex。复数包含实部和虚部。
定义 一个名为 Complex 的类,包含两个私有成员变量 real(实部)和 imag(虚部),以及一个公共构造函数用于初始化这两个值。
class Complex {
private:
double real; // 实部
double imag; // 虚部
public:
// 构造函数,默认参数为 0
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
// 用于打印结果的辅助函数
void display() const {
cout << "(" << real << ", " << imag << "i)" << endl;
}
// 声明友元函数,以便后续重载 << 运算符(可选)
friend ostream& operator<<(ostream& os, const Complex& c);
};
2. 重载加法运算符 (+)
加法运算符的目的是将两个复数对象相加。我们需要返回一个新的 Complex 对象,该对象包含两个操作数实部与虚部的和。
编写 成员函数 operator+,它接受一个 Complex 类型的常量引用作为参数。
实现 逻辑:将当前对象的实部与参数对象的实部相加,虚部同理,然后返回 这个新的临时对象。
$$ (a + bi) + (c + di) = (a+c) + (b+d)i $$
// 在类定义内部添加
Complex operator+(const Complex& other) const {
// 返回一个新的 Complex 对象
return Complex(this->real + other.real, this->imag + other.imag);
}
3. 重载减法运算符 (-)
减法与加法非常相似,只是将运算逻辑从相加改为相减。
编写 成员函数 operator-,同样接受一个 Complex 类型的常量引用。
实现 逻辑:用当前对象的实部减去参数对象的实部,虚部同理。
$$ (a + bi) - (c + di) = (a-c) + (b-d)i $$
// 在类定义内部添加
Complex operator-(const Complex& other) const {
return Complex(this->real - other.real, this->imag - other.imag);
}
4. 重载乘法运算符 (*)
复数乘法比加减法稍复杂,需要应用分配律和虚数单位 $i^2 = -1$ 的性质。
编写 成员函数 operator*。
实现 逻辑:
- 计算实部:
real * other.real - imag * other.imag(因为 $i \times i = -1$,这里要减去)。 - 计算虚部:
real * other.imag + imag * other.real。
$$ (a + bi) \times (c + di) = (ac - bd) + (ad + bc)i $$
// 在类定义内部添加
Complex operator*(const Complex& other) const {
double r = this->real * other.real - this->imag * other.imag;
double i = this->real * other.imag + this->imag * other.real;
return Complex(r, i);
}
5. 重载除法运算符 (/)
复数除法通常通过有理化分母来实现,即将分母中的虚数消除。
编写 成员函数 operator/。
实现 逻辑:
- 首先计算分母:$c^2 + d^2$。
- 检查 分母是否为 0,避免除以零错误。
- 计算实部:$(ac + bd) / (c^2 + d^2)$。
- 计算虚部:$(bc - ad) / (c^2 + d^2)$。
$$ \frac{a + bi}{c + di} = \frac{(ac + bd) + (bc - ad)i}{c^2 + d^2} $$
// 在类定义内部添加
Complex operator/(const Complex& other) const {
double denominator = other.real * other.real + other.imag * other.imag;
if (denominator == 0) {
throw runtime_error("Division by zero"); // 简单的错误处理
}
double r = (this->real * other.real + this->imag * other.imag) / denominator;
double i = (this->imag * other.real - this->real * other.imag) / denominator;
return Complex(r, i);
}
6. 完整代码与测试
将上述所有部分组合起来,并在 main 函数中进行测试。
复制 以下完整代码到你的 C++ 开发环境中。
编译 并运行 程序,观察输出结果。
#include <iostream>
#include <stdexcept>
using namespace std;
class Complex {
private:
double real;
double imag;
public:
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
// 重载 +
Complex operator+(const Complex& other) const {
return Complex(this->real + other.real, this->imag + other.imag);
}
// 重载 -
Complex operator-(const Complex& other) const {
return Complex(this->real - other.real, this->imag - other.imag);
}
// 重载 *
Complex operator*(const Complex& other) const {
double r = this->real * other.real - this->imag * other.imag;
double i = this->real * other.imag + this->imag * other.real;
return Complex(r, i);
}
// 重载 /
Complex operator/(const Complex& other) const {
double denominator = other.real * other.real + other.imag * other.imag;
if (denominator == 0) {
throw runtime_error("Division by zero");
}
double r = (this->real * other.real + this->imag * other.imag) / denominator;
double i = (this->imag * other.real - this->real * other.imag) / denominator;
return Complex(r, i);
}
// 重载输出运算符 << 以便直接打印对象
friend ostream& operator<<(ostream& os, const Complex& c) {
os << "(" << c.real << ", " << c.imag << "i)";
return os;
}
};
int main() {
Complex c1(4.0, 3.0); // 4 + 3i
Complex c2(2.0, 1.0); // 2 + 1i
Complex sum = c1 + c2;
Complex diff = c1 - c2;
Complex prod = c1 * c2;
Complex quot = c1 / c2;
cout << "c1: " << c1 << endl;
cout << "c2: " << c2 << endl;
cout << "c1 + c2 = " << sum << endl;
cout << "c1 - c2 = " << diff << endl;
cout << "c1 * c2 = " << prod << endl;
cout << "c1 / c2 = " << quot << endl;
return 0;
}
7. 运算符重载机制解析
当编译器遇到 c1 + c2 时,它会将其转换为函数调用。下图展示了 c1 + c2 的执行流程。
8. 成员函数 vs 非成员函数选择指南
在上面的例子中,我们将运算符重载为成员函数。但在某些情况下,推荐使用非成员函数(友元函数)。以下是两者的对比,帮助你做决定。
| 特性 | 成员函数 | 非成员函数 |
|---|---|---|
| 参数数量 | 二元运算符只需 1 个参数(隐含 this 指针) |
二元运算符需要 2 个参数 |
| 左侧操作数 | 必须是当前类的对象 | 可以是兼容类型(如 int + Complex) |
| 对称性 | 不对称(c1 + 5 可以,5 + c1 不行) |
对称(5 + c1 和 c1 + 5 都可以) |
| 访问权限 | 直接访问私有成员 | 需要通过 friend 声明访问私有成员 |
决策建议:
- 如果运算符修改左操作数(如
=、+=),使用成员函数。 - 如果运算符不修改操作数且需要类型转换对称性(如
+、-、*、/),优先使用非成员函数(友元)。
将 operator+ 改写为非成员函数的示例:
// 在类外部定义
class Complex {
// ... 其他成员 ...
friend Complex operator+(const Complex& c1, const Complex& c2);
};
// 非成员函数实现
Complex operator+(const Complex& c1, const Complex& c2) {
return Complex(c1.real + c2.real, c1.imag + c2.imag);
}
注意:对于复数这种纯数学对象,使用成员函数或非成员函数都是可接受的,只要保持一致性即可。但对于涉及不同类型混合运算的场景(如 Complex + int),非成员函数会更灵活。

暂无评论,快来抢沙发吧!