C++ 运算符重载:自定义类型的运算扩展
在 C++ 中,运算符重载是静态多态的重要体现。它允许我们为类或结构体重新定义运算符的行为,让自定义对象能像内置类型(如 int、double)一样参与运算。这不仅能简化代码书写,提升可读性,还能统一操作风格,降低学习成本。
不过要注意,运算符重载不会改变运算符的优先级和结合性,也不会改变操作数的个数。理解这一点,是掌握重载的关键。
核心语法:成员函数与全局函数
运算符重载的本质是函数重载,分为成员函数重载和全局函数重载两种形式。
成员函数重载
将运算符函数定义为类的成员时,一元运算符没有参数,二元运算符只有一个参数(右侧操作数)。
class ClassName {
public:
ReturnType operatorOp(ParamList) {
// 自定义逻辑
}
};
全局函数重载
作为全局函数时,一元运算符有一个参数,二元运算符有两个参数。如果需要访问私有成员,需将其声明为友元。
ReturnType operatorOp(ParamList) {
// 自定义逻辑
}
常见运算符实现细节
二元运算符:以 + 为例
下面是一个 Point 类的示例,演示如何重载加法运算符。
成员函数方式
#include <iostream>
using namespace std;
class Point {
public:
int x, y;
Point(int x = 0, int y = 0) : x(x), y(y) {}
// 成员函数重载 + 运算符
Point operator+(const Point& p) {
return Point(this->x + p.x, this->y + p.y);
}
void print() {
cout << "(" << x << ", " << y << ")" << endl;
}
};
int main() {
Point p1(1, 2), p2(3, 4);
Point p3 = p1 + p2; // 等价于 p1.operator+(p2)
p3.print(); // 输出 (4, 6)
return 0;
}
全局函数方式
这种方式更灵活,特别是当左侧操作数不是当前类对象时。
#include <iostream>
using namespace std;
class Point {
public:
int x, y;
Point(int x = 0, int y = 0) : x(x), y(y) {}
void print() {
cout << "(" << x << ", " << y << ")" << endl;
}
// 声明友元函数
friend Point operator+(const Point& p1, const Point& p2);
};
// 全局函数重载 + 运算符
Point operator+(const Point& p1, const Point& p2) {
return Point(p1.x + p2.x, p1.y + p2.y);
}
int main() {
Point p1(1, 2), p2(3, 4);
Point p3 = p1 + p2; // 等价于 operator+(p1, p2)
p3.print(); // 输出 (4, 6)
return 0;
}
一元运算符:前置与后置 ++
前置 ++ 先自增后使用,后置 ++ 先使用后自增。区别在于后置版本需要一个 int 占位参数来区分。
前置 ++
#include <iostream>
using namespace std;
class Counter {
private:
int count;
public:
Counter(int c = 0) : count(c) {}
// 成员函数重载前置 ++
Counter& operator++() {
this->count++;
return *this; // 返回引用支持链式操作
}
void show() {
cout << "计数:" << count << endl;
}
};
int main() {
Counter c(5);
++c; // 等价于 c.operator++()
c.show(); // 输出 计数:6
Counter c2 = ++c;
c2.show(); // 输出 计数:7
return 0;
}
后置 ++
注意这里的 int 参数只是占位符,实际调用时传入 0。
#include <iostream>
using namespace std;
class Counter {
private:
int count;
public:
Counter(int c = 0) : count(c) {}
// 成员函数重载后置 ++
Counter operator++(int) {
Counter temp = *this; // 保存当前状态
this->count++;
return temp; // 返回自增前的副本
}
void show() {
cout << "计数:" << count << endl;
}
};
int main() {
Counter c(5);
Counter c2 = c++; // 等价于 c.operator++(0)
c.show(); // 输出 计数:6
c2.show(); // 输出 计数:5
return 0;
}
关系运算符与输入输出
关系运算符(==, < 等)通常用于比较逻辑。而 << 和 >> 由于左侧操作数是流对象,必须用全局函数加友元的方式。
#include <iostream>
#include <string>
using namespace std;
class Person {
private:
string name;
int age;
public:
Person(string name = "", int age = 0) : name(name), age(age) {}
// 声明友元函数
friend ostream& operator<<(ostream& os, const Person& p);
friend istream& operator>>(istream& is, Person& p);
};
// 重载 << 运算符
ostream& operator<<(ostream& os, const Person& p) {
os << "姓名:" << p.name << ",年龄:" << p.age;
return os; // 返回流对象支持链式输出
}
// 重载 >> 运算符
istream& operator>>(istream& is, Person& p) {
is >> p.name >> p.age;
return is; // 返回流对象支持链式输入
}
int main() {
Person p;
cout << "请输入姓名和年龄:" << endl;
cin >> p; // 等价于 operator>>(cin, p)
cout << "你输入的信息:" << p << endl; // 等价于 operator<<(cout, p)
return 0;
}
限制条件与注意事项
并非所有运算符都能重载。以下情况需要特别注意:
-
禁止重载的运算符:
- 成员访问运算符
. - 成员指针访问运算符
.* - 作用域解析运算符
:: - 条件运算符
?: - 预处理运算符
#
- 成员访问运算符
-
必须使用成员函数的运算符:
- 赋值运算符
= - 函数调用运算符
() - 下标运算符
[] - 箭头运算符
->
- 赋值运算符
-
赋值运算符的深拷贝陷阱: 编译器默认生成的赋值运算符是浅拷贝。如果类中包含指针成员,必须手动重载以实现深拷贝,否则会导致内存泄漏。
#include <iostream>
#include <cstring>
using namespace std;
class String {
private:
char* str;
public:
String(const char* s = "") {
str = new char[strlen(s) + 1];
strcpy(str, s);
}
~String() {
delete[] str;
}
// 重载赋值运算符,实现深拷贝
String& operator=(const String& s) {
if (this == &s) return *this; // 防止自赋值
delete[] str; // 释放旧内存
str = new char[strlen(s.str) + 1];
strcpy(str, s.str); // 拷贝新数据
return *this; // 返回引用支持链式赋值
}
void show() {
cout << str << endl;
}
};
int main() {
String s1("Hello C++");
String s2;
s2 = s1; // 调用重载的 = 运算符
s2.show(); // 输出 Hello C++
return 0;
}
实战案例:复数运算
设计一个 Complex 类,重载加减乘及输出运算符。
#include <iostream>
using namespace std;
class Complex {
private:
double real; // 实部
double imag; // 虚部
public:
Complex(double real = 0, double imag = 0) : real(real), imag(imag) {}
// 成员函数重载 + 运算符
Complex operator+(const Complex& c) {
return Complex(this->real + c.real, this->imag + c.imag);
}
// 成员函数重载 - 运算符
Complex operator-(const Complex& c) {
return Complex(this->real - c.real, this->imag - c.imag);
}
// 成员函数重载 * 运算符
Complex operator*(const Complex& c) {
double r = this->real * c.real - this->imag * c.imag;
double i = this->real * c.imag + this->imag * c.real;
return Complex(r, i);
}
// 友元函数重载 << 运算符
friend ostream& operator<<(ostream& os, const Complex& c);
};
// 实现 << 运算符重载
ostream& operator<<(ostream& os, const Complex& c) {
if (c.imag >= 0) {
os << c.real << " + " << c.imag << "i";
} else {
os << c.real << " - " << -c.imag << "i";
}
return os;
}
int main() {
Complex c1(3, 4), c2(1, -2);
Complex c3 = c1 + c2;
Complex c4 = c1 - c2;
Complex c5 = c1 * c2;
cout << "c1 = " << c1 << endl;
cout << "c2 = " << c2 << endl;
cout << "c1 + c2 = " << c3 << endl;
cout << "c1 - c2 = " << c4 << endl;
cout << "c1 * c2 = " << c5 << endl;
return 0;
}
运行结果如下:
c1 = 3 + 4i
c2 = 1 - 2i
c1 + c2 = 4 + 2i
c1 - c2 = 2 + 6i
c1 * c2 = 11 - 2i
开发规范建议
在实际项目中,遵循以下规范能让代码更易维护:
- 保持语义一致:重载后的行为应与内置类型语义相近,避免误导使用者。
- 优先使用成员函数:对于一元运算符和复合赋值运算符(如
+=),优先用成员函数。 - 输入输出用全局友元:
<<和>>必须用全局函数 + 友元。 - 深拷贝处理:含指针成员的类,务必重载赋值运算符。
- 避免过度重载:只重载真正需要的运算符,增加复杂度得不偿失。
合理使用运算符重载,能让 C++ 代码既简洁又优雅,这是面向对象编程中不可或缺的技能。

