上一章讲解的是运算符的重载,那这一章就是讲讲运算符重载的用法,具体的体现在哪些方面--
先看下面的一串代码:
class Data
{
public:
Data(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1(2022, 2, 16);
Data d2(2015, 2, 4);
d1 = d2;
return 0;
}
我们想要将对象d2赋值给d1,我们用内置运算符(=),但我们知道内置运算符不能在自定义类型上用,那我们就只能用上章的运算符的重载了
赋值拷贝函数 :
我们先写一个 operator= 函数
//d1=d2 赋值拷贝函数
void operator=(const Data d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
那么我们现在有问题了,这个赋值拷贝函数有没有返回值呢?????
当然是有的了 --- 为什么呢
我们换一种思路来看 d1=d2
int i,j,k
i=j=k=10;
上面的代码执行操作顺序: 将10赋值给k ,在将k返回,将k赋值给j ,在将j返回,把j赋值给i
最终完成3个数的赋值操作
所以现在清楚了 d1=d2肯定是有返回值的了 ,返回值是d1
改进后代码
//d1=d2 赋值拷贝函数
Data operator=(const Data d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
因为我们this指针指向对象d1地址,我们解引用返回的就是d1对象,我们用(Data)接受
我们的程序貌似没问题了,也能运行起来(可有没有想过 我们这里将d1对象返回,可是函数返回值的操作也是将返回值拷贝传出去,那么这里也就涉及到了拷贝构造函数)
我们 d1=d2=d3 那么这里按代码执行顺序 ,d3赋值给d2 返回d2(一次),d2赋值拷贝给d1,返回d1(又一次调用),那么我们总共要调用2次拷贝构造函数
用Data来接收返回值 : 运行效率也就有折扣(影响了运行速度,同时消耗了资源)
那我们可以用引用的方式,减少了返回值的拷贝
//d1=d2 赋值拷贝函数
Data& operator=(const Data d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
这样子我们的赋值拷贝函数也就基本完成了
追求严谨的我们还可以想到一种极端情况 : d1=d2,自己赋值给自己,我们可以在赋值拷贝中加入一个判断
//d1=d2 赋值拷贝函数
Data& operator=(const Data d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
这样子我们的赋值拷贝函数就非常完美了
我们在简单说一下 : d1=d2 底层其实是转化成 d1.operator(const Data&d)
编译器会先在 类内找operator= 函数 在去全局找(当然去全局找的话,有的话,必须把私有的成员变量设成公有)一般是不会写在全局内的,破坏了类的封闭性
我们最后在说一下拷贝构造函数和赋值拷贝函数的区别(都是默认成员函数)
1.拷贝构造函数是一个已经存在的对象拷贝初始化一个马上实例化的对象
2.赋值拷贝函数是已经存在的个对象之间的赋值拷贝
总结以上的内容:
1.参数类型
2.返回值类型 (*this 返回对象)
3.检测是否自己给自己赋值
4.如果一个类没有显示自定义赋值运算符重载,编译器会自动生成一个
编译器默认生成的赋值重载,跟拷贝构造做的事情完全类似:
1.内置类型的成员会完成字节序的拷贝(浅拷贝)
2.自定义类型成员变量调用自己类内的赋值拷贝函数