5.运算符重载
5.1 规则
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
class Date { public: Date(int year = 0, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; }; int main() { Date d1(2023, 2, 3); Date d2(2023, 3, 4); return 0; }
创建 d1 和 d2 两个对象,时间分别为 2023.2.3 和 2023.3.4 ,如何比较它们的天数的先后,或者是算它们中间相差的天数?
对于平常情况下,我们可以手算,但是对于计算机呢?如何直接用运算符来比较?可不可以直接比较它们的先后?可以不可以让 d2 的天数直接减去 d1 的天数?例如 d1 > d2 ,d2 - d1 ?
在 C++ 中,只支持对内置类型的运算符计算,并不支持直接对自定义类型进行运算符计算,但是通过运算符重载可以达到这个效果 。
运算符重载:
函数名字为:关键字operator后面接 需要重载的运算符符号
函数原型:返回值类型 operator 操作符(参数列表)
返回值与参数:
对于返回值:不同的运算符重载函数,返回值是不同的,例如 > 就是 bool 类型;- 就是 int 类型
对于参数操作数有几个操作符,就有几个操作数
对于一个比较大小运算符重载的参数需要注意几点:
bool operator>(const Date& d1, const Date& d2);
重载的是 > ,1.对于这个符号有两个操作数;2.参数写成引用的形式,减少空间的消耗;3.加上 const 修饰,也防止这两个对象由于引用的原因被修改。
接下来,我们写出这个运算符重载:但是这迎来一个问题 :运算符重载在类外部访问不到成员变量,这样就使得运算符重载不起作用了。那么我们就要相除解决方案。
方法 1(可以但不推荐):
去掉 Date 类中成员变量的 private 的私有,我们先用了再说:这时,调试,然后到 d1 < d2 ,f11 进入运算符重载,对于这边的调用有两种方式:编译器看到自定义类型 d1 < d2 去找是否重载了运算符,如果找到了,就转换为 operator< (d1, d2) 调用函数,找不到就 报错
方法 2:
方法 1破坏了封装性,所以不太可行。所以可以写在类的里面 ,但是这里会有一个问题:
写成成员函数时,成员函数默认会有一个 this 指针,但是对于运算符重载的参数个数等于操作数个数,<有两个,现在就有三个了,所以错了。
例如如果直接调用 operaror< 函数时:
Date d1; d1.operator<(d2);
d1 作为 this 指针被传过去,d2 被作为另一个参数,但是这里对于我们当前的函数来说,有三个参数:this, d1, d2 ,参数不匹配了!
所以需要修改:
bool operator>(const Date& d) { if (_year > d._year) { return true; } else if (_year == d._year) { if (_month > d._month) { return true; } else if (_month == d._month) { if (_day > d._day) { return true; } } } return false; }
这时 d1 > d2 ,会先去到类的成员里面找,找到了转换为 d1.operator>(d2),若类中没找到,再到全局找,找到转换为 operator>(d1, d2) ,进行调用。
对于上面两种 operator> 全局的和类中的可以同时存在(参数不同构成重载),若同时存在,则调用类中成员的,和就近原则没什么关系,取决于编译器的实现机制;但是这种情况一般不存在,因为成员变量一般都是 private 私有的,全局的并没有用。
对于运算符重载的规则:
不能通过连接其他符号来创建新的操作符 :比如operator@
重载操作符必须有一个类类型参数
用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
.*(没用过,类似于访问解引用,重点记,选择题容易考) ::(作用域访问符) sizeof ?:(三目运算符) .(访问自定义类型) 注意以上5个运算符不能重载。经常在笔试选择题中出现。
返回值类型不一定是内置类型,例如 Date operate++() 返回值是日期
运算符重载的返回值必须写
赋值运算符在类中不显式实现时,编译器会生成一份默认的,此时用户在类外再将赋值运算符重载为全局的,就和编译器生成的默认赋值运算符冲突了,故赋值运算符只能重载成成员函数
5.2 赋值运算符重载
赋值运算符重载是对于两个已经存在对象之间的赋值拷贝,本质是一个运算符重载函数。
简单写一下:
// d1 = d3 void operator=(const Date& d) { _year = d._year; _month = d._month; _day = d._day; }
可以完成功能,但是不符合连续赋值的含义:例如 i = j = k = 10 ,是将 10 赋给 k ,k 赋给 j ,j 赋给 i ,最后赋值后的返回值没有变量接收,就把值丢弃,通过这种方式 完成连续赋值 。
但是下图呢?由于 operator= 没有返回值,为void,这样就不可行了,所以需要修改 :
// d1 = d3 Date operator=(const Date& d) { _year = d._year; _month = d._month; _day = d._day; return *this; }
根据上方的连续赋值规则,返回的应该是 左操作数 ,即 d1 ,也就是 this 指针指向的对象,即 *this ;但是对于 *this 的返回这里由于是传值返回,而 *this 指向的是一个对象,所以会调用拷贝构造函数
class Date { public: Date(int year = 0, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } Date(const Date& d) { cout << "进行拷贝构造" << endl; } // d1 = d3 Date operator=(const Date& d) { _year = d._year; _month = d._month; _day = d._day; return *this; // 调用拷贝构造 } private: int _year; int _month; int _day; }; int main() { Date d1(2023, 2, 3); Date d2(2023, 3, 4); Date d3(2022, 2, 3); d3 = d2 = d1; // 两次赋值,共计两次拷贝构造 return 0; }
因此返回时,最好使用 引用返回 来提高返回效率,因为出作用域 *this 仍然存在,所以引用返回完全没问题。
// d1 = d3 Date& operator=(const Date& d) { _year = d._year; _month = d._month; _day = d._day; return *this; }
此刻不进行拷贝构造
但是还不是最终版本,因为可能会写错为 d1 = d1 ,自己给自己赋值 ,虽然代码并没有问题,但是浪费了赋值的过程,所以可以再加一个检查,形成最终版本 :
Date& operator=(const Date& d) { if (this != &d) // this 和 d 的地址相等 { _year = d._year; _month = d._month; _day = d._day; } return *this; }
总结一下赋值运算符重载格式 :
参数类型:const T&,传递引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回*this :要符合连续赋值的含义
用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝
编译器默认生成的赋值重载,跟拷贝构造做的事情完全类似 :
内置类型成员,会完成字节序1值拷贝(浅拷贝)
自定义类型成员,会调用它的 operator= 赋值重载补充:
Date d2 = d1; // 拷贝构造 or 赋值重载? • 1
两个已经存在的对象才是赋值重载,d2 并不存在,所以是拷贝构造
总结 :
以上默认生成的四个默认成员函数,不能在类外面直接定义为全局,否则会与自动生成的默认成员函数冲突,但可以类内申明,类外定义。析构和构造处理机制基本类似;拷贝构造和复制重载处理机制基本类似。
构造和析构:
内置类型不做处理
自定义类型会调用对应的构造/析构
拷贝构造和赋值重载:
内置类型完成浅拷贝,按字节序拷贝
自定义类型去调用它的拷贝构造和赋值重载
6.总结:
今天我们认识并具体学习了类和对象的默认成员函数,分别为:构造函数、析构函数、拷贝构造函数、赋值操作符重载的知识。接下来,我们将继续学习类和对象的相关知识。希望我的文章和讲解能对大家的学习提供一些帮助。
当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~