C++运算符重载是赋予自定义类型对象使用内置运算符能力的重要特性,通过重载可以让类的对象支持加减乘除、比较、输入输出等运算,让代码更符合直觉,减少冗余的函数调用。运算符重载本质上是函数调用,只是把函数名定义为operator加上对应的运算符符号。

运算符重载的基本规则
不是所有运算符都可以重载,以下运算符不允许重载:::(作用域解析)、.(成员访问)、.*(成员指针访问)、?:(三目运算符)、sizeof。重载运算符时不能改变运算符的优先级、结合性,也不能改变操作数的个数,比如双目运算符重载后还是需要两个操作数。
重载运算符有两种常见形式:作为类的成员函数,或者作为类的友元函数,两种方式的选择取决于是否需要访问类的私有成员,以及操作数的顺序要求。
作为类成员函数重载运算符
当运算符的左操作数是当前类的对象时,适合用成员函数形式重载。下面以自定义复数类为例,重载加法运算符和输出运算符的前置++。
#include <iostream>
using namespace std;
class Complex {
private:
double real; // 实部
double imag; // 虚部
public:
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
// 重载加法运算符,作为成员函数,左操作数是Complex对象,右操作数是传入的参数
Complex operator+(const Complex& other) const {
return Complex(real + other.real, imag + other.imag);
}
// 重载前置++运算符,没有参数,返回递增后的对象引用
Complex& operator++() {
real += 1;
imag += 1;
return *this;
}
// 重载后置++运算符,带一个int类型占位参数,返回递增前的对象副本
Complex operator++(int) {
Complex temp = *this;
real += 1;
imag += 1;
return temp;
}
// 打印复数内容的函数
void print() const {
cout << real << " + " << imag << "i" << endl;
}
};
int main() {
Complex c1(1.5, 2.5);
Complex c2(3.0, 4.0);
// 调用重载的+运算符,等价于c1.operator+(c2)
Complex c3 = c1 + c2;
c3.print(); // 输出4.5 + 6.5i
// 前置++测试
++c1;
c1.print(); // 输出2.5 + 3.5i
// 后置++测试
Complex c4 = c2++;
c2.print(); // 输出4.0 + 5.0i
c4.print(); // 输出3.0 + 4.0i
return 0;
}
作为友元函数重载运算符
当运算符的左操作数不是当前类的对象,或者需要对称处理两个操作数时,适合用友元函数形式重载。最典型的场景是重载流插入<<和流提取>>运算符,因为左操作数是ostream或istream对象,不是自定义类的对象。
#include <iostream>
using namespace std;
class Complex {
private:
double real;
double imag;
public:
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
// 声明友元函数,允许该函数访问类的私有成员
friend ostream& operator<<(ostream& os, const Complex& c);
friend istream& operator>>(istream& is, Complex& c);
// 重载乘法运算符,作为友元函数,支持对称运算
friend Complex operator*(const Complex& c1, const Complex& c2);
};
// 重载流插入运算符,输出复数
ostream& operator<<(ostream& os, const Complex& c) {
os << c.real << " + " << c.imag << "i";
return os;
}
// 重载流提取运算符,输入复数
istream& operator>>(istream& is, Complex& c) {
is >> c.real >> c.imag;
return is;
}
// 重载乘法运算符,复数乘法规则:(a+bi)(c+di) = (ac-bd)+(ad+bc)i
Complex operator*(const Complex& c1, const Complex& c2) {
double real = c1.real * c2.real - c1.imag * c2.imag;
double imag = c1.real * c2.imag + c1.imag * c2.real;
return Complex(real, imag);
}
int main() {
Complex c1, c2;
cout << "请输入两个复数的实部和虚部:" << endl;
cin >> c1 >> c2; // 使用重载的>>运算符
cout << "第一个复数:" << c1 << endl; // 使用重载的<<运算符
cout << "第二个复数:" << c2 << endl;
Complex c3 = c1 * c2; // 使用重载的*运算符
cout << "两个复数相乘的结果:" << c3 << endl;
return 0;
}
两种重载方式的对比
可以通过下面的表格快速区分两种重载方式的适用场景:
| 对比项 | 类成员函数重载 | 友元函数重载 |
|---|---|---|
| 左操作数要求 | 必须是当前类的对象 | 可以是任意类型 |
| 访问私有成员 | 天然可以访问 | 需要声明为友元 |
| 常见适用场景 | 单目运算符、赋值运算符、左操作数为类对象的双目运算符 | 流运算符、左右操作数都可能不是类对象的双目运算符 |
| 参数数量 | 双目运算符只需要1个参数(左操作数隐式传递) | 双目运算符需要2个参数 |
注意事项
- 赋值运算符=、下标运算符[]、函数调用运算符()、成员指针访问运算符->必须作为类成员函数重载,不能作为友元函数。
- 重载运算符时不要违背运算符原本的语义,比如重载+运算符就不要实现减法的逻辑,避免代码可读性下降。
- 如果重载了算术运算符,通常建议同时重载对应的复合赋值运算符,比如重载了+就重载+=,保持接口的一致性。
- 不要重载逗号运算符和取地址运算符,这两个运算符的内置语义已经足够合理,重载后反而容易引发歧义。
掌握运算符重载后,可以让自定义类型的使用体验和内置类型几乎一致,在需要频繁对自定义对象做运算的场景下,能大幅简化代码逻辑,提升开发效率。