C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值

目录
类的6个默认成员函数
构造函数
析构函数
析构函数使用场景
场景1:不需要清理,但会执行默认析构函数
场景2:需要析构函数
默认生成析构函数特点
构造函数和析构函数的顺序问题
拷贝构造函数
特征
拷贝构造在传值传参和传引用传参的区别
拷贝构造在传值返回和传引用返回的区别
运算符重载
运算符重载应用
赋值运算符重载
赋值重载连续赋值



类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片


构造函数
我们在使用类时,一般会先使用初始化函数,但是有人会忘记进行初始化,有些情况下忘记初始化会导致程序崩溃或出现一些错误C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

C++中为了解决这个问题,提出了构造函数
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
其特征如下:
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
5.一个类里面的默认构造只能有一个,不然调用的时候编译器会凌乱
构造函数的函数名和类名相同,此时并未使用初始化函数,传参的时候直接写到对象名后面
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

构造函数也满足缺省参数的特性
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

这俩种同时存在会存在歧义,因为如果不传参数,此时编译器会凌乱,不知道调用那个函数
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

以下这种情况也会报错,这是因为有俩个构造函数
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

这种情况也是因为有俩个构造函数C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

一般这三种方式,只写一个
构造函数写栈
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成(如果没有构造函数,编译器自动生成一个构造函数,如果有构造函数,编译器就不会自动生成)
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

此时会自动默认生成,但是产生的值是随机值
C++类型分类:1.内置类型:int/double/char/指针等等
2.定义类型:struct/class主要是类类型
默认生成构造函数,对内置类型不做处理,对自定义类型成员回去调用它的默认构造函数,
这是C++早期的一个缺陷,默认生成构造函数,本来应该内置类型也一并处理
C++11打了补丁这样做
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片


class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } void Print() { cout << _year << "-" << _month << "-" << _day << endl; }private: int _year ; // 注意这里不是初始化,给缺省值 int _month ; int _day ; }; typedef int DataType; class Stack { public: Stack(int capacity=4) { cout << "Stack(int capacity = 4)" << endl; _array = (DataType*)malloc(sizeof(DataType) * capacity); if (NULL == _array) { perror("malloc申请空间失败!!!"); return; }_size = 0; _capacity = capacity; } void Push(DataType data) { // CheckCapacity(); _array[_size] = data; _size++; } private: DataType* _array; int _capacity; int _size; }; // C++类型分类: // 内置类型/基本类型:int/double/char/指针等等 // 自定义类型:struct/classclass MyQueue { private: Stack _st1; Stack _st2; }; int main() { MyQueue q(); return 0; }


C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片


C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

默认构造函数有三类:
1.我们不写,编译器自动生成的那个
2.我们自己写的全缺省构造函数
3.我们自己写的,无参的构造函数
默认构造函数特点:不传参数就可以调用
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

当我们做修改后,此时程序会报错,因为这里,既不是无参的,也不是全缺省的,而且由于构造函数名和类同名,这里相当于我们自己写了一个构造函数,所以编译器在这里不会生成默认构造函数
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片


析构函数
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作
析构函数是特殊的成员函数,其特征如下:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数(这是因为没有参数)。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

析构函数使用场景 场景1:不需要清理,但会执行默认析构函数
class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } void Print() { cout << _year << "-" << _month << "-" << _day << endl; } ~Date() { //日期类的没什么需要清理的 cout << "~Date()" << endl; } private: int _year = 1; // 注意这里不是初始化,给缺省值 int _month = 1; int _day = 1; }; void fun() { Date d1; } int main() { fun(); return 0; }

此时程序执行完fun函数
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

再按F11,程序直接来到了析构函数这里
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片


最终结果
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

默认的析构函数什么都不做
场景2:需要析构函数
默认的析构函数不会处理这里,这里的数组在堆上
typedef int DataType; class Stack { public: Stack(int capacity=4) { cout << "Stack(int capacity = 4)" << endl; _array = (DataType*)malloc(sizeof(DataType) * capacity); if (NULL == _array) { perror("malloc申请空间失败!!!"); return; } _size = 0; _capacity = capacity; } void Push(DataType data) { // CheckCapacity(); _array[_size] = data; _size++; } ~Stack() { free(_array); _capacity = _size = 0; _array = nullptr; } private: DataType* _array; int _capacity; int _size; }; void fun() { Date d1; Stack st; } int main() { fun(); return 0; }

C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

执行完fun函数后,先进入Stack的析构函数,再进入Date的析构函数
之前博客里写栈需要写初始化Init函数和释放空间的Destory函数,而在C++中写一个构造函数和析构函数不需要调用即可,析构函数就会代替Destory的作用来释放空间(要自己写)
默认生成析构函数特点
跟构造函数类似,内置类型不处理,自定义类型处理,自定义类型会调用自己的构造 ,指针属于内置类型,指针不处理,因为指针有的是指向动态开辟空间,有的指向一个数组,还有文件指针
当写栈,队列,链表,顺序表,二叉树用自己写的析构函数就比较方便
构造函数和析构函数的顺序问题
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片
先构造的后析构,后构造的先析构
这是因为这里的内容存在栈中,要满足先进后出
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

main 先调用f1,f1再调用f2,然后f2销毁返回f1,满足先进后出
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

先初始化全局的,当进入main函数后按顺序初始化
对于析构,变量销毁后就进行析构,aa2和aa1在栈区,栈帧结束后要清理资源先清理aa2,再清理aa1,全局变量和静态变量在函数结束以后才销毁,之后清理资源,所以先清理aa0再清理aa3
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

构造顺序:3 0 1 2 4 1 2
析构顺序:2 1 2 1 4 0 3

拷贝构造函数
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
特征
拷贝构造函数也是特殊的成员函数,其特征如下:
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝

C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

对于get1,形参是实参的一份的临时拷贝,get2,形参是实参的别名
参数也是类类型,用d1去初始化d即传参的时候,就需要构造函数(初始化函数),get1的构造是拷贝构造,get2还没传参的时候就已经构造好了,在Date d1这条语句里已经构造好了
验证上面的结论
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

这条语句之后按F11,直接跳到拷贝构造这里来
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

【C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值】
先构造d1,再给get1构造C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片


C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

若想把d1拷贝过去,这俩种写法都可以
若拷贝构造函数这样写,则会进入死循环,因为形参必须是类类对象的引用
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片
解决这种传参问题有俩种办法:1.引用
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片


2.指针(这种方式不是拷贝构造),虽然也能完成,但是不建议这种方法,这种方法比较奇怪,更容易出现错误
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

有时候拷贝构造函数容易写错,所以要加const
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

正确形式C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

未定义拷贝构造
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

对内置类型按直拷贝进行拷贝的:就是一个字节,一个字节拷贝过去
对自定义类型,调用自定义类型自己的拷贝构造函数完成
注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。
拷贝构造还有深浅拷贝问题,后面的博客里会写
浅拷贝举例
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

这里能正常拷贝,但是在析构的时候会崩溃
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

这是因为这里执行了俩次析构函数,由于这里是直拷贝,st和st1指向了同一块空间,而这快空间被释放了俩次, 因为这里是内置类型,指针是内置类型,把*array所指向的空间给st1拷贝过去了,然后st1和st指向了同一块空间,析构的时候又free了俩次,直接崩溃

拷贝构造在传值传参和传引用传参的区别
#include using namespace std; class A { public: A(int a = 0) { _a = a; cout << "A(int a=0)>>" <<_a<< endl; } A(const A& aa) { _a = aa._a; cout << "拷贝构造A(int a=0)>>" << _a << endl; } ~A() { cout << "~A()>>" <<_a<< endl; } private: int _a; }; void func1(A aa) {} void func2(A& aa) {}

C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

传值传参有一次拷贝构造,这个拷贝构造是针对aa1的拷贝构造
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片


引用传参没有拷贝构造
拷贝构造在传值返回和传引用返回的区别
#include using namespace std; class A { public: A(int a = 0) { _a = a; cout << "A(int a=0)>>" <<_a<< endl; } A(const A& aa) { _a = aa._a; cout << "拷贝构造A(int a=0)>>" << _a << endl; } ~A() { cout << "~A()>>" <<_a<< endl; } private: int _a; }; A fun3() { static A aa; return aa; } A& fun4() { static A aa; return aa; } int main() { A aa1(1); fun3(); cout << endl; fun4(); return 0; }

C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

传值返回时,进行了一次拷贝构造
第一个:A(int a=0)>>1是A aa1(1)的默认构造
第二个:A(int a=0)>>0是fun3函数里面对 static A aa的默认构造
拷贝构造A(int a=0)>>0是fun3函数执行return语句时,对aa的拷贝构造
这是因为传值返回在返回时,会拷贝一份aa,然后返回的是拷贝的aa,对于aa本身,在函数结束后就会销毁,对于这份拷贝的aa,就要调用拷贝构造函数
对于拷贝的aa,也是有生命周期的,它的生命周期在main函数fun3(); 这一行,当这一行调用结束之后,就要拷贝的aa进行析构
稍作修改,使观察更清晰
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

对于引用返回,不需要拷贝构造函数,直接调用构造函数,调用过程如下图
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

析构过程如下C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

这里aa(3)和aa(4)本身在静态区,所以后析构,如果aa出了fun4()的作用域就销毁,那么引用返回就有问题,前面博客里有提到过
运算符重载
这里有俩个日期,d1和d2,我们比较它们的大小关系
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

C++中规定:内置类型可以直接使用运算符运算,编译器知道如何运算,自定义类型无法直接使用运算符,编译器不知道如何运算
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表),返回值类型由运算符特点决定,如比较大小一般返回true或false,若想知道差值,返回int
注意:
不能通过连接其他符号来创建新的操作符:比如operator@重载操作符必须有一个类类型参数用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this.*::sizeof?:. 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

我们可这样来对日期判断是否相等,但是这里会报错,因为日期这些是私有的
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

解决方法:
1.写一个函数来获取这些私有数据 ,月和天也同理
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

将上面的私有数据改为共有,再进行测试
using namespace std; class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; cout << "构造函数" << endl; } //Date(const Date& d)//拷贝构造函数 //{ // _year = d._year; // _month = d._month; // _day = d._day; //} void Print() { cout << _year << "-" << _month << "-" << _day << endl; } int _year = 1; // 注意这里不是初始化,给缺省值 int _month = 1; int _day = 1; }; int main() { Date d1(2022,7,23); Date d2(2022, 8, 23); cout<<(d1 == d2) << endl; return 0; }

C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

编译器实际会将cout<<(d1==d2)< 本质是函数调用
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

编译器再工作时,如果发现d1,d2是内置类型,直接转换为相对应的指令,如果不是内置类型就看它是否满足运算符重载的条件,如果满足就执行
为什么不直接写一个调用函数呢?
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

如果写成这样,就没什么价值了,写成运算符重载,就能像内置类型一样去调用运算符,写成运算符重载可读性更好,如果写成函数,有人会不规范写函数名,导致其他人看不懂
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

在传参的时候,如果没有引用就会进行拷贝构造,如果是深拷贝,就会很麻烦,所以在传参的时候要引用,为了防止别人写错,最好加上const
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片


但运算符重载,还存在一个问题,就是内置类型,一般是私有,上面为了演示效果我们改为了共有,现在改回来,但是会报错
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

为了解决这个问题,我们把运算符重载函数写道类里面,还会报错,参数太多
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

参数太多是因为,还有一个隐藏的参数this指针
我们做以下修改即可
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

_year==d2._year,这里的_year实际是this->_year,this就是d1
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片


运算符重载应用
对于日期类,我们可以比较大小,也可以去查询N天以后/以前是几几年几月几号
思路:1.直接把天数加到day
2.若day超过了该月的总天数,用day-该月总天数,再给月+1,12月要把1,给年加1
int GetMonthDay(int year, int month) { static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; //多次调用的时候,每次都要创建数组,现在改为静态,放到静态区if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))//闰年2月 { return 29; } else { return days[month]; } } Date operator+(int day) {_day += day; while (_day > GetMonthDay(_year,_month)) { _day -= GetMonthDay(_year, _month); ++_month; if (_month == 13)//12月特殊处理 { _month = 1; _year++; } } }

这样写需要一个返回值,但是我们把日期全部加到了_year,_month,_day,因此要把这些给传回去,我们直接return *this,this是指针,*this是对象
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

先测试一下
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

现在写的这个,改变了d1,如果不想改变d1,把结果返回回去,做如下修改,main函数可以这样写,用一个东西来接收,符合拷贝构造
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片


我们拷贝一份d1,就行,这样就不会改变d1了
int GetMonthDay(int year, int month) { static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; //多次调用的时候,每次都要创建数组,现在改为静态,放到静态区if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))//闰年2月 { return 29; } else { return days[month]; } } Date operator+(int day) { Date ret(*this); ret._day += day; while (ret._day > GetMonthDay(ret._year,ret._month)) { ret._day -= GetMonthDay(ret._year,ret. _month); ++ret._month; if (ret._month == 13)//12月特殊处理 { ret._month = 1; ret._year++; } } return ret; } int main() { Date d1(2022,7,23); Date ret = (d1 + 50); Date ret1(d1 + 50); return 0; }

C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片


赋值运算符重载
1. 赋值运算符重载格式
参数类型:const T&,传递引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回*this :要复合连续赋值的含义
C语言中把一个数的值,赋值给另一个数,用=就可以,C++中把一个类的值,赋值给另一个类需要用到赋值重载
如这里,要把d3赋值给d1
void TestDate1() { Date d1(2022, 7, 24); Date d2(d1); //拷贝构造 Date d3(2022, 8, 24); d1 = d3; //赋值 } int main() { TestDate1(); return 0; }

#include using namespace std; class Date { public: //构造函数会频繁调用,所以放在类里面作为inline Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } void operator=(const Date& d) { _year = d._year; _month = d._month; _day = d._day; } private: int _year; int _month; int _day; };

C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

main函数里d1=d3; 会被转换成d1.operatpr=(&d1,d3);
传d1地址是因为有this指针
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片


赋值重载连续赋值
写成连续赋值就会报错 C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

对于普通变量这种连续赋值是可以的
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片


连续赋值是这样的:k=j=i,把i赋值给j,然后把j的返回值赋值给k
d2=d1=d3,也遵循上面的道理
d3赋值给d1,d1的返回值赋值给d2
这里应该这样修改,this是d1的指针(地址),*this就是d1
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

此时不会报错 ,如果不引用传参就要调用好多次拷贝构造
为了防止有人写成d2=d2,我们加上判断条件,d2=d2这条语句编译器是不会对其报错的
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

赋值运算符只能重载成类的成员函数不能重载成全局函数
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

写成全局的会报错
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重只能是类的成员函数。
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
屏蔽掉赋值重载函数后,仍然可以拷贝
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

添加一个类进行测试
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

此时调用,结论跟上面一模一样,自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

其实相面的time也不需要赋值重载,会默认生成一个赋值重载
对于栈这样的类型,需要写赋值重载
#include"date.h" typedef int DataType; class Stack { public: Stack(size_t capacity = 10) { _array = (DataType*)malloc(capacity * sizeof(DataType)); if (nullptr == _array) { perror("malloc申请空间失败"); return; } _size = 0; _capacity = capacity; } void Push(const DataType& data) { // CheckCapacity(); _array[_size] = data; _size++; } ~Stack() { if (_array) { free(_array); _array = nullptr; _capacity = 0; _size = 0; } } private: DataType* _array; size_t _size; size_t _capacity; }; int main() { Stack st1; Stack st2; st2.Push(1); st2.Push(2); st1 = st2; return 0; }

因为默认的赋值重载,会对内置类型,实行直拷贝,指针也属于内置类型,如果对指针直拷贝,会导致俩个指针指向同一块空间,在析构的时候会析构俩次,所以会报错
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

对于MyQueue这样的类,可以不写赋值重载,因为如果栈的赋值重载写好了,MyQueue直接可以躺平
C++|C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值
文章图片

对于拷贝构造和赋值重载一些类需要些,比如栈
一些类不需要写,如Date这样的类,默认完成直拷贝/浅拷贝,如mYqueue不需要写,默认会调用自定义类型Stack的拷贝和赋值

    推荐阅读