运算符重载
C语言中,运算符(操作符) 是只能对 内置类型数据或表达式
进行操作的
而 C++ 中,引入了 运算符重载
,使同类对象之间可以使用运算符,进而提高代码可读性
否则对象之间,一个简单的比较大小就得需要长代码实现. 如果需要多次比较,将十分痛苦
运算符重载定义
运算符重载
其实是一种具有特殊函数名
的函数,其 返回值类型
与 参数类型
同一般函数一样
但是 运算符重载函数的函数名格式为:operator运算符
即,operator + 需要重载的运算符
则函数的原型格式就为:返回值类型 operator运算符(参数列表)
并且,运算符重载还需要严格遵守一些规则:
不能创造新的运算符
:比如 通过operator@
想要赋予@
一定的功能重载运算符必须至少有一个自定义类型的操作数
:操作符必须要有操作数重载后的运算符不能改变原有含义
:不能将原来内置类型+
的功能,重载之后改为 对象之间相减的功能.*
、::
、sizeof
、? :
、.
以上5个运算符不能重载(笔试、面试会考)
![举个栗子](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/%E4%B8%BE%E4%B8%AA%E6%A0%97%E5%AD%90.jpeg#pic_center)
以 日期类判断大于
为例:
![date-202206231736](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/date-202206231736.png)
判断日期类大于,则函数定义就是这样的:
bool operator>(const Date& d1, const Date& d2) //比较大小不需要改变数值,所以const修饰,&可以节省资源
{
// 日期比较大小需要遵循实际
return (d1._year > d2._year
|| (d1._year == d2._year && d1._month > d2._month)
|| (d1._year == d2._year && d1._month == d2._month && d1._day > d2._day));
}
当然因为C++类有封装特性,所以 运算符重载函数是可以定义在类内
的,不过与定义在类外有一定的区别:
- 由于类内存在隐含的
this指针
,所以参数要比类外少一个,参数要比运算符所需操作数少一个,且默认 this指针 为第一个参数
(这也是重载需要严格遵守的第五条规则)
在类内定义操作符重载:
![image-20220623175907224](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/image-20220623175907224.png)
操作符重载是一种函数,一般函数应该传参使用
:
但是,操作符这样用非常的反逻辑,所以其实操作符正常逻辑
使用也是没有问题的
重载后的操作符,可以按照 正常的逻辑 使用
但其实,编译器还是会把使用操作符的语句,自动转换成这样d1.operator>(d2)
或 这样operator>(d1,d2)
,不用手动操作
赋值运算符重载
赋值运算符重载函数 其实与 拷贝构造函数 类似
![举个栗子](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/%E4%B8%BE%E4%B8%AA%E6%A0%97%E5%AD%90.jpeg#pic_center)
日期类的赋值运算符重载,内容也非常的简单,但是有一些需要非常注意的问题
:
![image-20220623182635689](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/image-20220623182635689.png)
赋值运算符重载,确实与拷贝构造函数相似,但是 为什么赋值运算符重载函数,需要返回赋值后的结果
?
当然是因为,内置类型的赋值运算符(=)
结果也是需要当作返回值返回的,因为需要实现连等(连续赋值)操作:
所以,赋值操作的结果要作为返回值返回,以便下一次赋值使用
,所以,将 *this
作为返回值返回
返回值是
Date&
类型的,使用引用是因为可以节省资源
但是并不是任何时候都可以使用引用类型作为返回值类型,因为 函数内的局部变量出了作用域就销毁了,如果再用&
只会造成错误
这里使用&
,是因为this指针
没被销毁
其实,在介绍类的默认成员函数时,有提到赋值重载函数也是 类的默认成员函数
这也就意味着,一个类,如果没有显式定义赋值重载函数,编译器也会自动生成一个赋值重载函数,赋值操作按字节拷贝完成
也就是说,对于成员变量简单的类来说,其实大部分都不用手动实现 默认成员函数
但是对于成员变量稍微复杂一点的变量来说,默认成员函数都是要手动实现的
思考
思考 此语句调用 赋值重载函数 还是 拷贝构造函数(d1
已存在):
Date D = d1;
回答之前,先对比一下 赋值重载 和 拷贝构造 具体用于什么环境下:
- 赋值重载:将一个对象的内容 赋值于 另一个对象
- 拷贝构造:在对象实例化时,将一个对象的内容 初始化至 正在实例化的对象
这样一对比,其实就已经知道 Date D = d1
调用的是哪个函数了
这个语句调用的是 拷贝构造函数
,因为这是在对象实例化时,要进行的初始化
。编译器会分析出来并自动调用拷贝构造函数
![举个栗子](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/%E4%B8%BE%E4%B8%AA%E6%A0%97%E5%AD%90.jpeg#pic_center)
![operator=](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/operator=.gif)
可以看到, Date d2 = d1;
这个语句被执行时调用的是 拷贝构造函数
;
而 d2
被实例化之后,再执行 d2 = d1
,则调用 赋值重载函数
.
日期类及运算符重载实现
上面已经对日期类实现了 大于与赋值运算符的重载
待实现的还有:==
、>=
、!=
、<
、<=
、+
、-
、+=
、-=
、++
、--
等
下面就从 ==
开始逐一实现
==
判断两对象是否相等非常的简单:
其实,在实现了>
或 <
的重载 和 ==
的重载之后,其他的逻辑判断运算符,都可以直接复用 >
和==
或 <
和 ==
来实现
>=、!=、<、<=:
![carbon(3)](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/carbon%283%29.png)
+ (日期 + 天数)
对于日期类,实现 计算用运算符的重载,需要存在一定的意义
比如,两个日期相加 是没有什么实际意义的:2002.1.1
+ 2000.1.1
有什么实际意义呢?
所以,日期类重载 +
一般实现的是,日期 + 天数
的功能
不过,日期的计算需要参考实际情况:
1、3、5、7、8、10、12月:31天
;
4、6、9、11月:30天
;
2月:一般28天,闰年29天
由于日期的多变,所以日期计算实现起来稍微复杂一点:
具体思路是:
- 先把需要加的天数全部加到对象的_day上
- 然后逐月计算,完成月份和年份的进位
在实现 +
的重载之前,先实现一个通过年月来计算月份天数的函数:
![image-20220623233756345](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/image-20220623233756345.png)
然后实现 +
重载:
![image-20220623235106933](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/image-20220623235106933.png)
年月份的进位逻辑是:
- 先将要加的天数加到
_day
上 - 循环判断当前的
_day
是否大于 当前月的实际天数 - 如果大于,
_day
就减去当前月的实际天数,并且月份加一(_month++)
- 每次
_month == 13
时,表示本年过完,需要年份加一(_year++),且月份归一(_month = 1)
- 直到
_day
不大于 当前月的实际天数,返回临时对象
需要将临时对象作为返回值返回,因为需要保证连加操作
实现过程中需要注意的就是:
- 不能直接操作
this指针
所指对象,否则会导致原对象数值改变 - 拷贝的临时对象出函数会被销毁,所以不能用
&类型
作为返回值
![image-20220624000753434](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/image-20220624000753434.png)
+= (日期 += 天数)
与 +
的重载一样,+=
通常也是实现 日期 += 天数
实现逻辑与 +
完全相同,不过 因为+=需要改变原对象数值
,所以可以直接操作 this指针 指向的对象
也就意味着,可以将 *this
作为返回值返回,并且可以使用 &类型
节省资源
![image-20220624001254127](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/image-20220624001254127.png)
![image-20220624000926349](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/image-20220624000926349.png)
- (日期 - 天数)
根据实际情况,日期类 -
的重载其实可以有两种重载方式:
- 日期 - 天数 —> 日期
- 日期 - 日期 —> 天数
先实现,日期 - 天数的重载:
![image-20220625153248999](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/image-20220625153248999.png)
年月日进位逻辑:
- 如果
需要减去的天数(day)
比当前_day
大或两者相等,进循环;否则直接拿_day - day
就可以结束了 - 进循环之后,先用
day
- 当前的_day
,并且月份 - 1(--_month)
,代表减一个月 - 减一个月后,如果
_month == 0
,就代表这一年没了,年份需要减一(--_year)
,并且设置月份从12月开始(_month = 12)
- 再将当前天数,设置为当前月份天数,继续循环判断
- 当
day
不再大于_day
时,退出循环,然后_day - day
. 至此,天数减完毕. 然后返回临时变量
需要将临时对象作为返回值返回,因为需要保证连减操作
实现过程中需要注意点与 +
的重载相同:
- 不能直接操作
this指针
所指对象,否则会导致原对象数值改变 - 拷贝的临时对象出函数会被销毁,所以不能用
&类型
作为返回值
![image-20220625154738623](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/image-20220625154738623.png)
-= (日期 -= 天数)
重载 -=
的逻辑与 -
相同,也不过是直接操作*this
而已
![image-20220625155431958](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/image-20220625155431958.png)
![image-20220625155016856](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/image-20220625155016856.png)
+、+=、-、-= 之间的复用及完善
在了解了 +
、+=
、-
、-=
的重载函数之后,其实可以发现 +
和+=
、-
和-=
之间其实是有一定的关系的
先以 +
和+=
为例对比:
两重载函数之间的代码几乎一模一样,只不过是一个操作 ret的成员变量
,另一个操作 this指向的成员变量
所以,这+
和 +=
其实是可以互相复用的:
![image-20220625161703878](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/image-20220625161703878.png)
这两种复用,都可以达成效果
不过,有一个问题:+= 复用 +
和 + 复用 +=
哪个更优一点?
答案是:+ 复用 +=
更优一点;
因为,+
的重载需要调用两次拷贝构造函数。直接实现+=
重载,是不需要调用拷贝构造函数的;如果 +=
复用 +
,就意味着 +=
同样需要调用两次拷贝构造函数;
如果是,+ 复用 +=
,则只有 +
需要调用拷贝构造函数
-
和 -=
同样是这样的关系
所以,+
的重载 一般复用 +=
实现,-
的重载 一般复用-=
实现
前面实现的 +
+=
-
-=
的重载 都只实现了对正数的操作,无法实现 对象 与 负数
的运算
不过,实现过 +
+=
-
-=
的重载之后,添加对负数的运算也只不过是加一个条件的事:
![carbon(14)](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/carbon%2814%29.png)
由于,复用了 +=
和 -=
,所以只需要在 +=
和 -=
的重载函数中添加条件就可以了
–、++
--
和 ++
分有前置和后置
C++ 语法规定,后置--
或 ++
,要在参数列表中添加一个 int
类型
所以--
和++
的重载非常的简单:
![image-20220625173605421](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/image-20220625173605421.png)
- (日期 - 日期)
日期类中 -
的重载还有一种意义,就是 两日期对象相减求相差多少天
不过,这个 -
的重载相对另一个,稍微简单一些:
![image-20220625175623412](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/image-20220625175623412.png)
const 成员
const
修饰的变量无法被修改
而之前介绍过,变量的权限可以缩小但是不能放大
比如:不能对被const修饰
的变量,起一个不被const修饰
的别名. 不能放大权限
由此,对于一个类来说,也会可能实例化一个被const修饰
的对象,或者函数的参数使被const修饰
的类
当一个对象 被const
修饰 时,表示对象的成员不能被修改,则其成员函数很可能就不能正常的使用了
![举个栗子](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/%E4%B8%BE%E4%B8%AA%E6%A0%97%E5%AD%90.jpeg#pic_center)
比如这样:
![image-20220625213349514](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/image-20220625213349514.png)
为什么呢?
因为,对象调用成员函数时,编译器会自动 将对象的地址作为this指针的内容传参到成员函数中
this指针
的类型(此类中)为 Date* const this
;
而此时的对象是被const
修饰的对象,此对象传参作为this指针
的内容,是使其从const Date
转换到Date
是属于权限的放大,是不能成立的
而 this指针
是由编译器控制的,无法手动更改
那么怎么样才能使 const对象
也正常的使用成员函数呢?
const修饰成员函数
const
其实可以修饰成员函数。当 const
修饰成员函数时,此函数被称为 const
成员函数
而,const
修饰成员函数时,所处的位置是在参数列表的后边,即:
![image-20220625215703184](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/image-20220625215703184.png)
当成员函数被 const
修饰时,即使是 const
对象也能够正常的使用成员函数了
这就也说明,const
修饰对象的地址可以作为 this指针
的内容了,这又是为什么呢?
其实是因为,const
修饰成员函数 就是 修饰了成员函数的 this指针
,即:
虽然无法直接改变 this指针
的类型,但是 const
修饰成员函数就是 const
修饰了 this指针
。
编译器会将 const
转换为修饰 this指针
思考1 *
-
const
对象 可以调用 非const
成员函数吗?经过上面的介绍,可以得出答案是:不可以
因为 非
const
成员函数 意味着this指针
未被const
修饰const
对象 无法传参,所以不可以 -
非
const
对象 可以调用const
成员函数吗?答案是可以,因为
const
对象可以传参给const
成员函数(const修饰的this指针)
-
const
成员函数 可以调用其他 非const
成员函数吗?答案是不可以,
const
成员变量,其this指针
也是被const
修饰的调用其他成员函数,也就是使
this指针
的内容作为参数又是一种
const
对象无法传参,所以不可以 -
非
const
成员函数 可以调用其他const
成员函数吗?答案是可以,
const
对象 可以传参给const
修饰的this指针 -
const
什么时候应该修饰成员函数?const
修饰成员函数,其实本质是:const 类* const this
使
this指针
的内容无法改变所以,其实
当成员函数不改变对象的成员变量时,一般用const修饰此成员函数
思考2 *
(d1 + 100).print();
结合上面+
的重载,这个语句有什么问题?(假设d1为已实例化的对象,print()是其成员函数且未被const修饰)
Date operator+(int day);
+
的重载的返回值是 Date类型的,即+
的重载是传值返回,而这意味着 计算完成的结果 其实被作为了一个临时的对象
存储了起来
在之前的文章中介绍过:
其实,函数一个局部变量作为返回值传值返回时,由于局部变量会被销毁,所以返回结果会被作为临时变量存储起来
这个临时变量是无法被修改的,具有常性
,即可看作为const
修饰的
而临时对象具有常性,所以是无法调用 非const
成员函数的
当前 VS 编译器无法演示 (Linux) 好像也无法演示
但是应是如此
&(取地址) 与 const&重载
类的六大默认成员函数,已经介绍了四个:构造函数、析构函数、拷贝构造函数、赋值重载函数
剩下的两个就是,取地址重载函数 和 const
修饰的取地址重载函数
这两个 默认成员函数绝大多数情况是不需要手动实现
的,编译器自动生成的就已经能够解决绝大多数的情况
除非,不想取地址成功
这两个重载函数的实现也非常的简单:
![image-20220626140128158](https://dxyt-july-image.oss-cn-beijing.aliyuncs.com/CSDN/image-20220626140128158.png)
这两个默认成员函数,都是在取对象地址时自动调用的.