C++类与对象(详解构造函数,析构函数,拷贝构造函数,赋值重载函数)

Posted _麦子熟了

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++类与对象(详解构造函数,析构函数,拷贝构造函数,赋值重载函数)相关的知识,希望对你有一定的参考价值。

文章目录


1.类的六个默认成员函数

如果一个类中什么成员都没有,简称为空类。但是,空类中真的什么都没有吗?并不是,任何类在什么都不写的时候,编译器实际上会自动生成以下6个默认成员函数。

1.默认成员函数是什么?

用户没有显示实现,编译器会自动生成的成员函数称为默认成员函数。

2.空类长啥样?

class Date; //这是一个空的日期类

3.6个默认成员函数

2.构造函数

2.1概念

我们先来看一个日期类(旧版的,继承C语言思想的):

class Date

public:
	//初始化函数
	void Init(int year, int month, int day)
	
		_year = year;
		_month = month;
		_day = day;
	//打印函数
	void Print()
	
		cout << _year << "-" << _month << "-" << _day << endl;
	
private:
	int _year;
	int _month;
	int _day;
;
int main()

	//构建对象,并调用初始化函数和打印函数
	Date d1;
	d1.Init(2022, 7, 5);
	d1.Print();
	Date d2;
	d2.Init(2022, 7, 6);
	d2.Print();
	return 0;

对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?C++的构造函数就做到了这一点,我们接下来学习构造函数的特性和使用方法!

2.2特性

构造函数是C++的特殊成员函数,需要注意的是,构造函数虽然叫做构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象

其特征如下(非常重要!!!)

1.函数名和类名相同
这里所说的构造函数无返回值是真的无返回值,而不是说返回值为void。

2.函数没有返回值
当你用类创建一个对象时,编译器会自动调用该类的构造函数对新创建的变量进行初始化。

3.对象实例化(即创建时)编译器自动调用对应的构造函数

4.构造函数可以重载(意思是一个类可以有多个构造函数,但是必须实现函数重载)

5.构造函数在对象整个生命周期只调用一次

6.无参的构造函数,全缺省的构造函数以及我们不写编译器自动生成的构造函数都称为默认构造函数,注意默认构造函数只能有一个
注意,有些同学可能认为,我们不写,编译器自动生成的构造函数才被称为默认构造函数,但实际不是这样的,无参的构造函数,全缺省的构造函数,我们不写,编译自动生成的默认构造函数都是默认构造函数。

比如,我们看一下代码,我们在类中同时实现了无参的构造函数和全缺省的构造函数,两者都属于默认的构造函数,但是我们说过了默认构造函数只能有一个,所以下面的代码测试是不通过的。

#include <iostream>
using namespace std;
class Date

public:
	Date()
	
		_year = 1900;
		_month = 1;
		_day = 1;
	
	Date(int year = 1900, int month = 1, int day = 1)
	
		_year = year;
		_month = month;
		_day = day;
	
private:
	int _year;
	int _month;
	int _day;
;
void Test()

	Date d1;//报错

报出的错误:

7.如果类中没有显示定义构造函数,则编译器会自动生成一个无参的默认构造函数,如果用户显示定义了,编译器就不会再生成了
讲到这个特性,有些人可能会想,既然我们不写,编译器会自动帮我们生成一个构造函数,那我们为什么还要写呢?为了解决大家这个疑问,我们来看一下以下代码以及运行结果:

#include <iostream>
using namespace std;
class Date

public:
	void Print()
	
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	
private:
	int _year;
	int _month;
	int _day;
;
int main()

	Date d1; // 在创建d1对象时,编译器会自动调用默认构造函数
	//(注意我们在类中没有写显示写构造函数)
	d1.Print();
	return 0;


代码运行结果:

诶?怎么打印出来的都是随机值呢?你不是说我们不显示写类的构造函数,编译器会自动帮我生成一个默认构造函数嘛?我们来看看编译器自动生成的构造函数的机制,就知道其中的原因啦!

编译器自动生成的构造函数的机制
1.编译器自动生成的构造函数对类的内置类型不做处理
2.对于自定义类型,编译器会再去调用它们自己的构造函数

C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char…,自定义类型就是我们使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。

我个人觉得这是C++构造函数不太完善的一点,如果编译器自动生成的默认构造函数能帮我们把内置类型初始化一下就好了。所以大多数情况下都需要我们自己去写一个类的构造函数。

2.3示例

1.以下代码示范了如何使用构造函数:【内置类型版】

#include <iostream>
using namespace std;
class Date

public:
	// 1.无参构造函数
	Date()
	
	// 2.带参构造函数
	Date(int year, int month, int day)
	
		_year = year;
		_month = month;
		_day = day;
	
private:
	int _year;
	int _month;
	int _day;
;
void TestDate()

	Date d1; // 调用无参构造函数
	Date d2(2015, 1, 1); // 调用带参的构造函数
	// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
	// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
	// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
	Date d3();

1.以下代码示范了如何使用构造函数:【自定义类型版】
虽说编译器会自动帮我们调用自定义类型的构造函数,但是要注意自定义类的构造函数我们还是需要去自己实现的。

#include <iostream>
using namespace std;
class Time

public:
	Time()
	
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	
private:
	int _hour;
	int _minute;
	int _second;
;
class Date

private:
	// 基本类型(内置类型)
	int _year;
	int _month;
	int _day;
	// 自定义类型
	Time _t;
;
int main()

	Date d;
	return 0;

2.4C++11中构造函数的变化

C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值

class Time

public:
	Time()
	
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	
private:
	int _hour;
	int _minute;
	int _second;
;
class Date

private:
	// 基本类型(内置类型)
	//内置类型成员变量在类中声明时可以给默认值
	int _year = 2023;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
;
int main()

	Date d;
	return 0;

3.析构函数

3.1概念

通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作

当一个类对象销毁时,局部变量也会随着该对象的销毁而销毁,例如前面讲解到的代码,用日期类创建了一个对象d1,当d1被销毁时,对象d1当中的局部变量_year/_month/_day会被编译器销毁。但是这并不意味着析构函数没有什么意义。像new,malloc动态开辟出来的内存空间,当该对象被销毁时,其中动态开辟的栈并不会随之被销毁,需要我们对其进行空间释放,这时析构函数的意义就体现了。

3.2特性

1.析构函数的函数名需要在类名前加上字符’~'

2.析构函数无参数,没有返回值;
不要认为没有返回值,就是void

3.对象生命周期结束时,C++编译器会自动调用析构函数
C++引入构造函数,析构函数的特性,大大降低了C语言中忘记初始化,忘记释放空间的bug的发生。

编译器自动生成的析构函数机制
 1、编译器自动生成的析构函数对内置类型直接销毁,会随着对象的销毁而销毁。
 2、对于自定义类型,编译器会再去调用它们自己的默认析构函数。

下面的程序我们会看到,编译器生成的默认析构函数,对自定类型成员调用它的析构函数。

class Time

public:
	~Time()
	
		cout << "~Time()" << endl;
	
private:
	int _hour;
	int _minute;
	int _second;
;
class Date

private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
;
int main()

	Date d;
	return 0;

程序运行结束后输出:~Time()
在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month,_day三个是内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁,main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数

注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数

4.一个类只有一个析构函数,如果我们不写,没有显示定义,则编译器会自动生成默认的析构函数

5.先构造的后析构,后构造的先析构
因为对象是定义在函数中的,函数调用会建立栈帧,栈帧中的对象构造和析构也要符合栈的先进后出的原则

6.注意:析构函数不能重载

3.3示例

在下面的代码中,简单实现了一个栈,在构造函数中,动态开辟了内存空间,实现了析构函数。

typedef int DataType;
class Stack

public:
	Stack(size_t capacity = 3)
	
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		
			perror("malloc申请空间失败!!!");
			return;
		
		_capacity = capacity;
		_size = 0;
	
	void Push(DataType data)
	
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	
	// 其他方法...
	
	//析构函数的写法
	~Stack()
	
		if (_array)
		
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		
	
private:
	DataType* _array;
	int _capacity;
	int _size;
;
void TestStack()

	Stack s;
	s.Push(1);
	s.Push(2);

7.如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏

4.拷贝构造函数

4.1概念

创建对象时,可否创建一个与已存在对象一某一样的新对象呢?答案是肯定的。

拷贝构造函数只有单个形参,该形参是对本类类型对象的引用(一般用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

#include <iostream>
using namespace std;
class Date

public:
	Date(int year = 0, int month = 1, int day = 1)// 构造函数:这里用了缺省参数
	
		_year = year;
		_month = month;
		_day = day;
	
	Date(const Date& d)// 拷贝构造函数的书写方式
	
		_year = d._year;
		_month = d._month;
		_day = d._day;
	
private:
	int _year;
	int _month;
	int _day;
;
int main()

	Date d1(2021, 5, 31);
	Date d2(d1); // 用已存在的对象d1创建对象d2
	//Date d2 = d1;//这种形式也会调用拷贝构造函数

	return 0;


4.2特性

1.拷贝构造函数是构造函数的重载
因为拷贝构造函数的函数名与类名相同,只参数不一样。

2.拷贝构造函数的参数只有一个,且必须使用引用传参,使用传值方式,编译器直接报错,因为会引起无穷递归调用

问题:为什么传值调用会引发无穷递归调用?

这样,我们先不看const的修饰,拷贝构造函数的形参就是Data data,然后传过来的是d1,实际上就是Data data(d1),大家看看这个形式熟不熟悉?这不就又是一次拷贝构造函数嘛,我们要的是传参,结果又要调用拷贝构造函数,即调用自己,所以会一直无穷递归下去。

总结:自定义类型在书写拷贝构造函数的时候,都加上引用&。

3.如果我们没有显示定义拷贝构造函数,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储,按字节为单位完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。而自定义类型是调用其自己的拷贝构造函数进行拷贝的。

#include <iostream>
using namespace std;
class Time

public:
	Time()
	
		_hour = 1;
		_minute = 1;
		_second = 1;
	
	Time(const Time& t)
	
		_hour = t._hour;
		_minute = t._minute;
		_second = t._second;
		cout << "Time::Time(const Time&)" << endl;
	
private:
	int _hour;
	int _minute;
	int _second;
;
class Date

private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
;
int main()

	Date d1;

	// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
	// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数,Date类默认生成的拷贝构造函数会调用Time类的拷贝构造函数。
	Date d2(d1);
	return 0;

问题:编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,我们还需要自己显示实现一个拷贝构造函数吗?注意:像日期类这种,成员变量是内置类型的,我们就不需要自己再显示实现一个拷贝构造函数,那么下面有涉及到申请动态内存资源的类呢?

我们来看一下,在拷贝构造函数中,容易造成的错误:一下的代码会崩溃。下面会详细讲解其崩溃的原因:

// 这里会发现下面的程序会崩溃掉
#include <iostream>
using namespace std;
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)
	
		_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 s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack s2(s1);
	return 0;

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;但是一旦涉及到资源申请,则拷贝构造函数一定要写,不然就变成了浅拷贝。

如何更改上述代码,使其正确调用拷贝构造函数?

在Stack类中显示定义上我们自己的拷贝构造函数,代码如下:

Stack(const Stack& st1)

	cout<<"const Stack& st"<<endl;
	_array=(int*)malloc(sizeof(int)*st1._capacity);
	if(_array==nullptr)
	
		perror("malloc fail\\n");
		exit(-1);
	
	//拷贝内容,按字节为单位进行拷贝,利用memcpy函数
	memcpy(_array,st1._array,sizeof(int)*st1._size);
	_size=st1._size;
	_capacity=st1._capacity;

这是运行结果:

4.3示例

拷贝构造函数典型调用场景

  • 使用已存在的对象创建新对象
  • 函数参数类型为类类型对象(注意不要引发无穷递归)
  • 函数返回值类型为类类型对象(因为返回时要构造临时变量,所以会调类的拷贝构造函数)
class Date

public:
	Date(int year, int minute, int day)
	
		cout << "Date(int,int,int):" << this << endl;
	
	Date(const Date& d)
	
		cout << "Date(const Date& d):" << this << endl;
	
	~Date()
	
		cout << "~Date():" << this << endl;
	
private:
	int _year;
	int _month;
	int _day;
;
Date Test(Date d)//函数参数类型为类类型对象(注意不要引发无穷递归)

	Date temp(d);//使用已存在的对象创建新对象
	return temp;//函数返回值类型为类类型对象(因为返回时要构造临时变量,所以会调类的拷贝构造函数)

int main()

	Date d1(2022, 1, 13);//调用构造函数
	Test(d1);
	return 0;

运行结果:

调用顺序:

为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。

4.4总结

什么时候需要深拷贝?什么时候需要浅拷贝?

1)浅拷贝:只对对象中成员进行简单赋值

2)深拷贝:类中一旦涉及资源申请,我们应该重新分配动态空间,进行深拷贝,即拷贝出来的对象只是内容与原对象一样,但两个对象是在不同的空间。

3)如果涉及到资源申请,没有写拷贝构造函数,在销毁对象时,两个对象的析构函数将对同一个内存空间释放两次,这就是出现错误的原因,即
浅拷贝操作不当是导致二次释放的常见原因

4)需要写析构函数的类—需要写深拷贝的函数构造
不需要写析构函数的类–默认生成的浅拷贝的函数构造就可以用

5.赋值运算符重载

5.1运算符重载

1.概念 :C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

2.函数名字为:关键字operator+后面接需要重载的运算符符号。
3.函数原型:返回值类型 operator

初始c++——类与对象

在这里插入图片描述

1.构造函数

1.1构造函数的定义

对于Date类,可以通过SetDate函数的方法给对象设置内容,可是我们每次创立一个对象后,才能去使用SetDate函数设置日期,这样未免太繁琐。如下:
在这里插入图片描述
那么我们可不可以创建Date对象的同时并给对象设置一个值?答案是有的,c++中给了我们一个构造函数就有这个功能。

构造函数是特殊的成员函数:
1.名字与类名相同,创建类类型对象时由编译器自动调用
2.在对象的声明周期只调用一次
在这里插入图片描述

1.2构造函数的特性

其主要特征如下:
1.名字与类名相同
2.无返回值
3.对象实例化时编译器会自动调用构造函数
4.构造函数可以重载

在这里插入图片描述

假如我们在类中没有写一个构造函数,那么我们调用对象时,编译器自动生成一个默认构造函数。一旦用户在类中定义了一个构造函数,则这个默认构造函数将不会生成。
在Data类中没有一个构造函数,当我们创建一个对象时,编译器会自动调用默认构造函数将a进行了初始化,只是a中的成员变量为随机值,使用无参的构造函数不需要+()。

在这里插入图片描述

无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参
构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。
默认构造函数的意思是:不需要传参数就能初始化对象。

内置类型(基本类型):int,char,double等,还有指针都是内置类型;
自定义类型:我们用struct,class自己定义的类型

关于编译器生成的默认成员函数,很多童鞋会有疑惑:在我们不实现构造函数的情况下,编译器会生成
默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但
是d对象year/month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么卵
用??
对于下面这个Data没有自定义一个构造函数,Date类中有基本类型和自定义类型,那么编译器怎么去调用构造函数呢?

在这里插入图片描述

一般建议我们定义出一个全缺省构造函数,因为它可以灵活应用,它既可以传参,也可以不需要传参也能够初始化对象。

在这里插入图片描述

2.析构函数

2.1概念

在c语言中,当我们要去创建栈去使用,当这个栈使用完后,我们要去调用StackDestory把这个栈中malloc创建的数组必须及时释放内存(free函数),并把该数组的地址给置为空,如果忘记去调用这个函数,那么就会发生内存泄露。
什么使内存泄露?
内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
所以c++中有一个析构函数就可以帮助我们去解决这个问题,当我们去创建一个对象栈后,当这个栈使用完后(被销毁时),编译器会去自动调用析构函数,去完成一些资源清理工作。(只要有使用malloc出一个数组,都需要做资源清理工作)

2.2 特性

1.析构函数是在在类名加~
2.一个类只能有一个析构函数,如果类中没有定义一个析构函数,则系统会自动生成一个默认的析构函数
3.无参数无返回值。
4.对象声明周期结束时会编译器会自动调用析构函数。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

假如我们自己没有写析构函数,那么编译器会去调用自己的析构函数,而这个析构函数对内置类型不做处理,对自定义类型会去调用它自己的析构函数。
在这里插入图片描述

3.拷贝构造函数

3.1 概念

拷贝构造函数在创建对象时能够(将一个已存在的相同类型的对象的所有值拷贝)给它。
如下:
创建d2的同时把d1的值拷贝给d2.
在这里插入图片描述

3.2 拷贝构造函数的特性

拷贝构造函数也是一个特殊的成员函数,它的特征如下:
1.拷贝构造函数是构造函数的一个重载形式
2.拷贝构造函数的参数只有一个,且参数必须为传引用,若传值的话会引发无穷的递归的调用拷贝函数
在这里插入图片描述
如果拷贝构造函数的参数为为传值,那么在传值的过程会去调用拷贝构造函数去拷贝一个临时变量,每一次调用都会去在调用拷贝构造函数,以此类推,则会引起无穷无尽的调用拷贝构造函数。

在这里插入图片描述

3.如果类中没有定义一个拷贝函数,则系统会默认一个拷贝构造函数,默认的拷贝构造函数对象按内存存储按字节完成拷贝,这种为浅拷贝,或值拷贝。
在这里插入图片描述

那么我们可不可以用编译器自动生成的拷贝构造函数去拷贝生成一个栈呢?
在这里插入图片描述
由上面的图片可以知道,用编译器自动生成的构造函数将s1的值拷贝给s2后,程序会奔溃,那么为什么会发生这样的情况呢?
首先我们得知道,在栈中这个对象栈中存储的什么内容。
在这里插入图片描述

当我们要把s1中的内容拷贝给s2时:
在这里插入图片描述
由于编译器默认的拷贝构造函数是浅拷贝,s1中的指向数组的地址直接拷贝给s2,这时就有两个指针指向这个数组,那么当程序结束后,编译器就会去调用析构函数,就会分别对s1和s2中的数组给free掉,由于malloc出的数组只能free掉一次,而s1和s2的指针是指向同一个数组,所以析构函数就会对这个数组使用两次free,最终程序会奔溃。

4.赋值运算符的重载

4.1 概念

在c语言中两个相同的内置类型(int,char,double,long,char*,int*,double*,long*等内置类型)可以直接使用运算符,比如:
我们定义一个int i,那么我们可以直接拿它来+100,或-100.

在这里插入图片描述

如果我们可不可以让自定义类型像内置类型一样去使用运算符呢?比如:
将(2021年6月4日)+100天后的日期得到的日期,或减-100天后的日期得到的日期。

在这里插入图片描述

c++中为了使自定义类型能够像内置类型一样去使用运算符,就有了一个运算符重载函数,当我们需要哪个运算符就去自定义哪一个运算符。

定义:运算符重载函数是为了增加代码的可读性,运算符重载是具有特殊函数名的函数, 也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表).
那么以下面+=运算符的重载来讲解:
在这里插入图片描述

4.2 实现日期类的运算符

4.2.1 日期+=天数

那么我们来实现着这个日期+=天数的赋值重载函数;
首先,我们知道一年中每个月的天数各不相同,有的月有30天,有的月有31天,闰年的二月有29天,所以我们需要先写一个函数能够知道某年某月来获取天数。
在这里插入图片描述
在函数前面加inline是把函数定义为内联函数,因为该函数需要经常调用,在数组前面添加一个stastic要把数组定义在静态区,这样就函数每次调用完以后,数组不会被销毁掉,这样就不会每次去调用函数就创建一次数组,以提高效率。

那么我们加完天数后该怎样去把它转化为准确的日期呢?我们以2021年6月4日加100天作为例子:
在这里插入图片描述
加完后天数为104天,这时候已经超出了6月的天数,这时候我们需要减去6月的天数(30天),然后月数加1变为7月,则为2021.7.74,这时候天数还是大于7月的天数,这时候我们在减去7月的天数(31天),月数在加1变为8月,则为2021.8.43,同样道理再减去8月的31天,最终结果为2021.9.12.
代码:
在这里插入图片描述
得到的结果为:
在这里插入图片描述

4.2.2日期+天数

在这里插入图片描述
由之前我写的博客可以知道,在类中的成员函数都隐藏着一个this指针,这个this指针是指向对象的地址。我们先通过拷贝构造函数将日期拷贝给tmp,然后tmp再去+=一个日期,最后返回tmp,这样原本的日期就不是发生改变。

4.2.3日期-=天数

在这里插入图片描述
这时候我们要需要判断日期是否小于等于0,如果小于等于0,那么我们需要对月数减1,然后在判断月数是否等于0,如果等于0,则年数减1,然后月数赋值为12,然后在加上该月中的天数,以上面为例:这时候的天数为-96,显然天数为负数,那么月数先减1后变为5,然后月数在加上5月的天数31天后变为-65,以此类推,直到天数变为正数。
代码如下:

在这里插入图片描述

4.2.4日期-天数:

在这里插入图片描述

4.2.5日期++和++日期:

由于前置++和后置++的赋值运算符重载的函数名是一样的,c++为了区分这两个运算符,则后置++的运算符重载函数中的参数里加一个int以便区分,前置++则不需要。如下:
在这里插入图片描述

4.2.6 两个日期的比较

判断日期>日期的函数:
在这里插入图片描述
判断日期==日期的函数:
在这里插入图片描述

判断日期<日期的函数:根据>=的反例是<,我们可以用上面已经实现好的两个函数去实现判断日期<日期。
在这里插入图片描述
判断日期>=日期:
在这里插入图片描述
判断日期<=日期:
在这里插入图片描述
判断日期!=日期:
在这里插入图片描述
由上面例子可以得出:实现日期判断大小的6个函数,我们只需要先实现判断日期>日期和日期==日期这两个函数,其它的函数直接调用这两个函数就可以,以后写这种判断大小的也是一样。

4.2.7日期1-日期2=天数

我们先找出较大的日期和较小的日期,然后用较大日期和较小日期进行比较,如果不相等,较小日期++,并count++,直到较小日期等于较大日期时,则count为它们相差的天数。如果日期1大于日期2,则返回一个正数,如果日期1小于日期2,则返回一个负数。
代码如下:
在这里插入图片描述

感谢你的点赞,关注,收藏,评论~

以上是关于C++类与对象(详解构造函数,析构函数,拷贝构造函数,赋值重载函数)的主要内容,如果未能解决你的问题,请参考以下文章

C++类与对象第二篇:(构造函数析构函数拷贝构造运算符重载取地址及const取地址操作符重载)

[ C++ ] C++类与对象之 类中6个默认成员函数

初始c++——类与对象

C++ ----类与对象(上)

C++入门拷贝构造函数详解

C++入门拷贝构造函数详解