🍉这种叫做浅拷贝
1、一个对象修改会影响另一个对象
2、 会析构两次,程序崩溃
像这种类,就不能用默认的了,要我们自己实现深拷贝 —— 后面专门讲解
对于自定义类型变量,确实会调用它的拷贝构造函数,我们可以验证 ——
class A { public: A(const A& a) { cout << "A(const A&)" << endl; } }; 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; A _a; }; int main() { Date d1(2002, 3, 7); Date d2(d1); return 0; }
六. 赋值运算符重载
我们知道内置类型可以直接使用运算符运算,但在默认情况下,C++是不支持自定义类型对象使用运算符,因为编译器也不知道运算规则
比如日期类:
Date d(2022, 10, 8); Date d(2022, 10, 26);
我想比较任意两个日期大小d1 < d2,想计算还有多少天国庆d2 - d1,都是没办法直接用运算符计算的。
为此,我们引入了运算符重载。
💦运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数
函数原型如下:
注:
不能通过连接其他符号来创建新的操作符:比如operator@
重载操作符必须有一个类类型参数
用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
.*(很少见)、 :: 、sizeof、 ? :(三目) 、. 注意以上5个运算符不能重载,这个经常在笔试选择题中出现
于是我们就实现了这样一个比较日期类大小的逻辑 ——
注:传参可以传值,但是在C++中建议传引用,这样可以减少拷贝提高效率(减少拷贝构造的消耗)。并且如果我们无需改变操作数,就用常引用const Date&,(防止我们在修改形参同时把实参也改了,同时const引用接收对象,权限缩小或者不变)
因为我的成员变量是private私有的,在类外不能访问,所以都出错了,那我们暂且先把访问修饰限定去掉
接下来有两个问题 ——
🔮1、d1==d2是怎么样调用这个函数的?
🔮2、成员变量私有在类外面访问不了,如何解决?
🧐问题1:如何调用
d1==d2是怎么样调用这个函数的?
经过调试,确实是调用了,实际上编译器会把d1 == d2转化为operator==(d1, d2)
但一般不会这样去显示写,但是这样就违背了我们增强可读性的初衷了,不然还不如直接写一个函数呢?
🧐问题2
成员变量私有在类外面访问不了,如何解决?
我们为了让程序运行简单粗暴去掉了访问修饰限定符private,这实际上破坏了封装性。
我们也可以在类中写上诸如int GetYear() {}这样的函数,这不破坏封装性,但还是有些麻烦,也不常用
int GetYear() { return _year; }
那我们把它放进类里面—— 又出现问题了
这是因为成员函数默认多了一个this指针
class Date { public: Date(int year = 0, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } bool operator==( const Date& x) { return _year == x._year && _month == x._month && _day == x._day; } //private: int _year; int _month; int _day; }; int main() { Date d1(2022, 10, 8); Date d2(2022, 10, 26); cout << (d1 == d2) << endl; cout << d1.operator==(d2)<< endl; //编译器处理-> d1.operator==(&d1,d2) return 0; }
当d1 == d2;我们这样调用它时,实际上会被编译器处理成d1.operator==(d2);
所以我们推荐这样写
cout << d1.operator==(d2)<< endl;
💦赋值运算符重载
🎨 赋值重载的概念
有了上面拷贝构造的基础,接下来的理解就轻松很多了
我们介绍了拷贝构造函数 —— 它是用一个已经存在的对象,拷贝初始化一个即将创建的对象
//拷贝构造 Date d1(2022, 10, 9); Date d2(d1);
接下来介绍的是赋值重载—— 它是两个已经存在的对象,之间进行拷贝赋值
//赋值重载 Date d1(2022, 10, 9); Date d3(2022, 10, 26); d1 = d3;
提问:这个是赋值重载还是拷贝构造?
Date d1(2002, 3, 7); Date d2 = d1;
根据定义出发 :只要一个存在的对象,这是拷贝构造
🎨赋值重载的实现细节
🔥1 .赋值运算符重载格式
参数类型:const T&,传引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回的效率(不用生成临时拷贝),有返回值目的是为了支持连续赋值
检测是否自己给自己赋值(判断)
返回*this :要复合连续赋值的含义
class Date { public: //构造会频繁的调用,所以直接放在类里面定义作为inline Date(int year = 2022, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //d1 = d3 赋值重载 //d1.operator=(d2); 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(2022, 10, 9); Date d2(d1); Date d3(2022, 10, 26); d1 = d3;//转化成 -> d1.operator(&d1, d3); }
但是这样写不够完美,原因有两点:
🐋1、不能连续的赋值
打比方:
int i = 0, j = 0, k = 2;
k = i = j =10;
对应到C++中就是,日期类的连续赋值,我们需要返回值
d1 = d2 = d3;
⚡传值返回——
// 返回的是d2,类型为Date Date operator=(const Date& d) { _year = d._year; _month = d._month; _day = d._day; //即返回d1 -- 返回了调用这个函数隐含的操作数 return *this; }
this是d2的地址,解引用*this就是返回值对象,这样就可以连续赋值了
⚡传值返回可以是可以,但是会生成一个临时拷贝的对象(占空间),况且这里出了作用域,d1(*this)还在,可以传引用返回:Date&
🐋2、自己给自己赋值,要判断
对于:自己给自己赋值
d2 = d2;
Date& operator=(const Date& d) { if (this != &d) { _year = d._year; _month = d._month; _day = d._day; } return *this; }
ps: 不能用对象 if (*this != d)去判断,会去调用operate!=,代价很大,而是用地址去判断
🔥如果我们没写,编译器默认生成的赋值重载,参考拷贝构造——
对于内置类型成员,会完成字节序值拷贝 —— 浅拷贝
对于自定义类型成员,会调用对应类的赋值运算符重载完成赋值
举例:
#include<iostream> using namespace std; class Time { public: Time() { _hour = 1; _minute = 1; _second = 1; } Time& operator=(const Time& t) { cout << "Time& operator=(const Time& t)" << endl;//为了调试看得到 if (this != &t) { _hour = t._hour; _minute = t._minute; _second = t._second; } return *this; } private: int _hour; int _minute; int _second; }; class Date { public: Date(int year = 2022, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //d1 = d3 赋值重载 //d1.operator=(d2); /*void operator=(const Date& d) { _year = d._year; _month = d._month; _day = d._day; }*/ private: //内置类型 int _year; int _month; int _day; // 自定义类型 Time _t; }; int main() { Date d1(2022, 10, 9); Date d2(d1); Date d3(2022, 10, 26); d2 = d1 = d3;//转化成 -> d1.operator(&d1, d3); }
🍅2 .赋值运算符只能重载成类的成员函数不能重载成全局函数
class Date { public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } int _year; int _month; int _day; }; // 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数 Date& operator=(Date& left, const Date& right) { if (&left != &right) { left._year = right._year; left._month = right._month; left._day = right._day; } return left; } // 编译失败: // error C2801: “operator =”必须是非静态成员
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的=赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数
·
七. 小总结
➰拷贝构造函数
➰赋值运算符重载
📢写在最后
英雄联盟世界赛要准备开打了,有爱看的兄弟吗?