在 C++ 面向对象编程中,运算符重载是赋予自定义类型与内置类型一致操作体验的核心机制。它让我们能够用 +、-、=、== 等直观的运算符操作对象,极大提升了代码的可读性和自然性。本文将从基础概念出发,深入讲解赋值运算符重载的要点,并通过完整的日期类实现,带你掌握运算符重载的实战技巧。
一、运算符重载基础
1.1 什么是运算符重载
运算符重载的本质是函数重载,它允许我们为自定义类型重新定义已有运算符的行为。编译器会根据操作数的类型,自动调用对应的重载函数。
1.2 重载规则与限制
• 不能发明新运算符:只能重载 C++ 已有的运算符,如 +、-、*、/ 等
• 不能改变运算符的优先级和结合性:比如 * 永远比 + 先计算
• 不能改变操作数个数:二元运算符重载后仍是二元,一元仍是一元
• 至少一个操作数是自定义类型:不能重载两个 int 之间的 + 运算
• 部分运算符不可重载:.、.*、::、?:、sizeof 等
1.3 两种重载形式
运算符重载有两种实现方式:成员函数和全局函数。
| 形式 | 特点 | 适用场景 |
|---|---|---|
| 成员函数 | 隐含this指针作为左操作数,参数少一个 | 一元运算符、赋值类运算符 |
| 全局函数 | 参数列表完整,需要访问私有成员时声明为友元 | 二元运算符(左操作数可能不是本类对象) |
二、赋值运算符重载
赋值运算符 = 是最特殊也最容易出错的运算符,它必须作为类的成员函数重载,不能是全局函数。
2.1 为什么需要重载赋值运算符
编译器会为每个类生成默认的赋值运算符,执行浅拷贝(按位复制)。当类中包含指针、动态分配的资源时,浅拷贝会导致:
• 多个对象指向同一块内存
• 析构时重复释放
• 一个对象修改会影响另一个对象
这时候就需要我们手动重载赋值运算符,实现深拷贝。
2.2 赋值运算符重载的标准写法
2.3 三个关键要点
1. 返回值是引用:返回 *this 的引用,支持 a = b = c 连续赋值
2. 检查自我赋值:避免 a = a 时释放了资源又去访问已释放的内存
3. 先释放后分配:先清理自身旧资源,再分配新空间拷贝数据
2.4 更安全的现代写法:复制并交换
异常安全的赋值运算符推荐使用 "copy and swap" 手法:
这种写法天然异常安全,且自动处理自我赋值,是 C++ 中的最佳实践之一。
三、日期类完整实现
下面我们通过一个完整的 Date 类,综合运用各种运算符重载,涵盖比较运算符、算术运算符、自增自减运算符、输入输出运算符等。
3.1 类的定义
3.2 比较运算符实现
比较运算符建议基于 < 和 == 来实现其他运算符,减少重复代码。
3.3 日期加减运算
3.4 自增自减运算符
前置和后置的区别在于参数:后置多一个 int 占位参数。
3.5 赋值运算符与输入输出
3.6 测试代码
四、总结与最佳实践
1. 能成员就成员,必要时友元:赋值、下标、函数调用运算符必须是成员;输入输出运算符必须是友元全局函数
2. 保持语义一致性:+ 就该做加法,不要写反直觉的逻辑
3. 遵循内置类型的行为:前置返回引用、后置返回值;赋值返回自身引用
4. 复用代码:用 += 实现 +,用 < 和 == 实现所有比较运算符
5. 警惕自我赋值:凡是涉及资源释放的赋值运算符,都要检查自我赋值
运算符重载是 C++ 强大表现力的来源之一,用好它能让你的类用起来像内置类型一样自然。但也要避免滥用,只有当运算符的含义清晰直观时才使用,否则不如用普通成员函数更明确。