1. 运算符重载的基本概念
运算符重载是C++一项强大的特性,它允许我们为自定义类型(类或结构体)重新定义运算符的行为。通过运算符重载,我们可以让自定义类型像内置类型一样使用标准的运算符语法,使代码更加直观和自然
从本质上讲,运算符重载是函数重载的一种特殊形式。当我们重载一个运算符时,实际上是在定义一个特殊的成员函数或全局函数,函数名由operator关键字后接要重载的运算符组成
1 2 |
|
运算符重载提供了语法糖,让代码更加直观和易读。比较以下两种写法:
1 2 3 4 5 |
|
第二种写法显然更加符合直觉,让自定义类型与内置类型有一致的操作方式
2. 算术运算符重载
2.1 加法运算符重载
在代码中,复数类的加法运算符重载展示了运算符重载的基本用法:
1 2 3 4 5 6 7 |
|
运算符重载有两种实现方式:成员函数形式和全局函数形式
成员函数形式隐含一个this指针参数,只需要一个显式参数:
1 2 3 4 5 6 7 8 9 |
|
全局函数形式需要两个显式参数,通常需要声明为类的友元函数以访问私有成员
1 2 3 4 |
|
代码中使用了全局函数形式,并将它们声明为友元函数,这样可以访问Complex类的私有成员real和image。
2.2 减法运算符重载
减法运算符的重载与加法类似,只需改变运算逻辑:
1 2 3 4 5 6 7 |
|
3. 流运算符重载
3.1 输出运算符<<重载
输出运算符<<的重载需要特别注意返回类型和参数类型。代码中实现了这一功能:
1 2 3 4 5 |
|
这里有几个关键点:
- 返回类型必须是
ostream&(引用),这样才能支持链式输出如cout << a << b << c; - 第一个参数是
ostream&类型,通常是cout或其变体 - 第二个参数是要输出的对象
- 函数返回输出流对象本身,使链式操作成为可能
如果返回void类型,将无法实现链式输出。这是因为cout << c << endl会被解析为(cout << c) << endl,如果cout << c返回void,那么void << endl就是非法的。
3.2 避免不必要的拷贝
使用const Complex& other而不是Complex other的好处:避免拷贝构造。当对象较大时,传引用可以显著提高性能。正确的声明应该是:
1 |
|
这里的const确保不会意外修改对象状态。
4. 自增运算符重载
自增运算符有前置和后置两种形式,需要分别处理。
4.1 前置自增运算符
1 2 3 4 5 |
|
前置++返回引用,这是为了保持与内置类型一致的行为。这样++a本身可以作为左值使用
4.2 后置自增运算符
1 2 3 4 5 6 |
|
后置++通过int参数与前置版本区分,这个参数仅用于区分,并不实际使用。它返回的是值而不是引用,因为返回的是局部对象,不能返回引用。
注释中提到的,后置++返回临时对象,这个对象在函数结束后会被销毁,所以不能返回引用。
5. 赋值运算符重载
赋值运算符重载需要特别注意深拷贝和自赋值问题。
1 2 3 4 5 6 7 8 9 10 11 |
|
这里有几个重要考虑:
- 检查自赋值:虽然代码中没有显式检查,但
a = a这样的自赋值应该安全处理 - 释放旧资源:在分配新资源前释放已有资源,防止内存泄漏
- 深拷贝:创建新内存并复制内容,而不是简单复制指针
- 返回引用:支持链式赋值
a = b = c
改进版本应该包含自赋值检查:
1 2 3 4 5 6 7 8 9 10 |
|
6. 关系运算符重载
关系运算符重载通常返回bool值,用于比较对象。
1 2 3 4 5 6 7 8 9 10 11 |
|
代码中通过比较点到原点的距离来定义<运算符,这是一种常见的做法。注意这些函数被声明为const,因为它们不应该修改对象状态。
7. 函数调用运算符重载
函数调用运算符()的重载创建了所谓的仿函数(functor)。
1 2 3 4 5 |
|
仿函数比普通函数更灵活,因为它们可以保持状态。如你的示例所示,每次调用都会增加data的值,这是普通函数无法做到的。
仿函数可以记录调用过程中的状态,比普通函数更加灵活,常用于STL算法中的定制行为
8. 运算符重载的规则与最佳实践
8.1 可重载的运算符
C++允许重载大部分运算符,包括:
- 算术运算符:
+,-,*,/,% - 关系运算符:
==,!=,<,>,<=,>= - 逻辑运算符:
&&,||,! - 赋值运算符:
=,+=,-=,*=,/= - 下标运算符:
[] - 函数调用运算符:
() - 流运算符:
<<,>> - 自增自减:
++,--
8.2 不可重载的运算符
有些运算符不能重载,包括:
- 成员访问运算符:
. - 成员指针访问运算符:
.* - 作用域解析运算符:
:: - 条件运算符:
?:(三目运算符) sizeof运算符typeid运算符
8.3 最佳实践
- 保持语义一致性:重载的运算符应该保持与内置类型相似的语义
- 考虑返回值类型:
- 算术运算符通常返回新对象
- 复合赋值运算符通常返回引用
- 关系运算符返回bool
- 正确处理常量性:不修改对象的函数应声明为
const - 遵循三/五法则:如果定义了拷贝构造函数、拷贝赋值运算符、析构函数中的一个,通常需要定义其他相关函数
9. 总结
运算符重载是C++面向对象编程的重要特性,它让自定义类型能够以更自然的方式集成到语言中。通过合理使用运算符重载,我们可以编写出更加直观、易维护的代码。
关键要点:
- 运算符重载的本质是函数重载,遵循函数重载的规则
- 选择成员函数还是全局函数形式取决于具体需求
- 流运算符
<<和>>通常重载为全局友元函数 - 赋值运算符需要处理自我赋值和深拷贝问题
- 前置和后置自增/自减运算符通过参数区分
- 函数调用运算符重载创建仿函数,可以保持状态
合理使用运算符重载可以极大提高代码的可读性和易用性,但也要避免滥用,保持运算符的直观语义。当你面对自定义类型需要类似内置类型的操作时,运算符重载是一个强大的工具。