C++类和对象: 赋值运算符重载
0. 前言
本章首先提出一个问题以及对应的解决方法, 但是这种解决方法会有缺陷
以此引出运算符重载来改进这个解决方法, 目的是为了更好的理解运算符重载概念以及运算符重载解决了什么问题
之后详细说明运算符重载, 然后再运算符重载的基础上介绍赋值运算符重载
1. 概念引入
如何比较两个日期类对象的大小?
一个比较容易想到的解法是用一个函数去实现, 如下例
#include <iostream> using namespace std; class Date { public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //private: int _year; int _month; int _day; }; bool Compare(const Date& x1, const Date& x2) { if (x1._year < x2._year) { return true; } else if (x1._year == x2._year && x1._month < x2._month) { return true; } else if (x1._year == x2._year && x1._month == x2._month && x1._day < x2._day) { return true; } return false; } int main() { Date d1(2025, 4, 25); Date d2(2023, 5, 25); cout << Compare(d1, d2) << endl; }
0表示假, d1 大于 d2
实际上, 这种解法并不好, 因为可读性很差, 看代码的人需要具体去看Compare函数的实现, 才能看懂这段代码Compare(d1, d2)在做什么
如果是这样 d1 < d2, 那么看代码的人可以清楚的明白这是在做比较, d1是否小于d2,这样就增强了可读性
但是, 自定义类型是不能使用运算符的, 因为编译器无法识别, 不能转换成指令
所以C++引入了运算符重载, 为了让自定义类型可以像内置类型一样使用运算符
下面来看一下, 如何使用运算符重载优化解法
#include <iostream> using namespace std; class Date { public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //private: int _year; int _month; int _day; }; // 重载运算符< bool operator<(const Date& x1, const Date& x2) { if (x1._year < x2._year) { return true; } else if (x1._year == x2._year && x1._month < x2._month) { return true; } else if (x1._year == x2._year && x1._month == x2._month && x1._day < x2._day) { return true; } return false; } int main() { Date d1(2025, 4, 25); Date d2(2023, 5, 25); cout << (d1 < d2) << endl; }
如图, 重载了运算符<, 对象之间就可以使用运算符<进行比较
这样方便使用, 同时又提高了可读性
下面详细说明运算符重载的概念与特性
2. 运算符重载
概念
运算符重载是具有特殊函数名的函数, 具有返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似
运算符重载的定义: 返回值类型 operator操作符(参数列表)
需要注意:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
- .* :: sizeof ?: . 注意以上5个运算符不能重载
在概念中提到, 运算符重载是一个函数, 那么在概念引入例子中为什么可以这样(d1 < d2) 调用函数 ?
实际上, (d1 < d2) == operator<(d1, d2), 它们最终会转换为一样的指令
因为编译器执行(d1 < d2), 会去自动调用operator<(d1, d2), 所以最终的指令都是一样的
概念引入的例子还有没有什么问题?
#include <iostream> using namespace std; class Date { public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //private: int _year; int _month; int _day; }; // 重载运算符< bool operator<(const Date& x1, const Date& x2) { if (x1._year < x2._year) { return true; } else if (x1._year == x2._year && x1._month < x2._month) { return true; } else if (x1._year == x2._year && x1._month == x2._month && x1._day < x2._day) { return true; } return false; } int main() { Date d1(2025, 4, 25); Date d2(2023, 5, 25); cout << (d1 < d2) << endl; }
还有几个问题, 需要一个一个来看
类中的成员变量, 应该设置为私有, 不允许在类外面访问
但是将成员变量设置为私有, 又会出现新的问题, 如上图, 在类外无法访问成员
解决这个问题, 可以把重载运算符函数放在类里面, 作为成员函数, 这样就可以通过this指针访问私有成员变量
但是这样做又会出现新的问题, 如上图
出现问题的原因是, operator<运算符函数只能有两个参数, 而这里有三个 ( x1 + x2 + 隐藏的this指针)
所以需要去掉一个参数, 如下
#include <iostream> using namespace std; class Date { public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } // 重载运算符< bool operator<(const Date& d) { if (_year < d._year) { return true; } else if (_year == d._year && _month < d._month) { return true; } else if (_year == d._year && _month == d._month && _day < d._day) { return true; } return false; } private: int _year; int _month; int _day; }; int main() { Date d1(2025, 4, 25); Date d2(2023, 5, 25); cout << (d1 < d2) << endl; cout << d1.operator<(d2) << endl; }
如图, 这样就解决了问题
注意: (d1 < d2) == d1.operator<(d2) d1调用成员函数operator<
3. 赋值运算符重载
概念引入
有了运算符重载的基础, 直接先看一个使用赋值运算符的例子
#include <iostream> using namespace std; class Date { public: // 构造 Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } // 拷贝构造 Date(Date& d) { _year = d._year; _month = d._month; _day = d._day; } bool operator<(const Date& d) { if (_year < d._year) { return true; } else if (_year == d._year && _month < d._month) { return true; } else if (_year == d._year && _month == d._month && _day < d._day) { return true; } return false; } // 赋值运算符重载 void operator=(const Date& d) { _year = d._year; _month = d._month; _day = d._day; } private: int _year; int _month; int _day; }; int main() { Date d1(2025, 4, 25); Date d2(2023, 5, 25); d1 = d2; //d1.operator=(d2); }
如图, d1 = d2, 使用赋值运算符重载将d2赋值给了d1
这里需要注意两个点:
1. d1 = d2, 实际上等于d1.operator=(d2)
2. 一定要搞清楚, 什么是拷贝构造函数, 什么是赋值运算符重载
拷贝构造函数是构造函数的重载形式, 它的作用是使用一个对象去初始化另外一个对象
赋值运算符重载是运算符函数, 它完成的是对象与对象之间的拷贝
它们两个是完全不同的概念, 不能混
上面举的例子实际上还存在不足
比如, 不能连续赋值
这是因为d2 = d1的值等于调用赋值运算符函数d2.operator=(d1)的返回值,也就是空
所以需要修改返回值, 这里先将返回值改为对象类型
如图, 连续赋值成功, 但是看调试控制台打印了两个copy,这是为什么?
这是因为函数返回值是一个对象(值), 它会去调用拷贝构造函数完成值拷贝
值返回的代价是很大的, 虽然再这里不会影响效率(因为在这里只拷贝24个字节), 但是如果是一个大对象呢?
这样就会消耗很多的时间与空间, 所以这里最好是引用返回, 因为引用返回不做拷贝, 直接返回, 提高效率
引用返回一定要注意返回对象的生命周期
比如这里, d2 = d1 ---> d2.operator=(d1) ---> 隐藏的this指针等于对象d2的地址 ---> 当生命周期结束d2依然存在, 所以这里可以使用引用返回
下面再进一步介绍赋值运算符重载
概念与特性
赋值运算符重载与构造函数和析构函数一个, 是一个默认的成员函数, 其完成对象与对象之间拷贝的工作
特性如下:
1. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝
2. 内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值
3. 赋值运算符只能重载成类的成员函数不能重载成全局函数
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!