【C++学习笔记】类和对象之构造函数、析构函数以及拷贝构造函数详解

🙊 构造函数🙊

试想以下当用 c 语言实现一个数据结构,一般都需要进行初始化销毁,如果忘记做初始化和内存清理的话,会造成内存泄漏等等问题,这十分的不方便,而 c 语言每次初始化和销毁的时候,都需要传递参数,所以 c++ 就对其进行了改进,让编译器自动来做这件事情,就有了前面介绍过的 this 指针。

而针对这个问题,初始化对象在 c++ 中有一个特殊的名字–构造函数,注意这里的构造函数虽然叫构造函数,但是并不是在构造一个对象,而是在初始化对象。

💖 构造函数的特征

构造函数是特殊的成员函数,是祖师爷钦点的成员函数,其特征如下:

1、函数名和类名相同

2、不用写返回值

3、对象实例化时编译器自动调用对应的构造函数

4、构造函数可以重载,一个类有多个构造函数,也就是说一个对象有多种初始化方式

💖 构造函数的使用方式及注意事项

以下是构造函数的代码,而根据构造函数的特征,这里写了两个可以构成函重载的构造函数。

class Stack
{
public:
	Stack()
	{
		cout << "Stack()" << endl;
		_a = nullptr;
		_size = _capacity = 0;
	}

	Stack(int n)
	{
		_a = (int*)malloc(sizeof(int) * n);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = n;
		_size = 0;
	}
private:
	// 成员变量
	int* _a;
	int _size;
	int _capacity;
};

此时主函数里面创建一个对象的时候,不需要再调用初始化函数,就可以直接进行对象的初始化。

int main()
{
	Stack st;

	return 0;
}

此时运行程序执行完第一条指令后,可以看到对象 st 被初始化了,这就说明构造函数是对象实例化的时候自动调用的。

在这里插入图片描述

而构造函数可以发生重载,如下面的这段代码,对象 st1 就调用的是第二个构造函数。

int main()
{
	Stack st;
	Stack st1(4);
	return 0;
} 

因为使用 c 的时候,会经常忘记调用初始化函数,所以 c++ 直接将其设计到类里面,就避免了忘记调用初始化函数而导致程序内存泄漏等现象。

注意:

Stack st1(4) 不可以理解为与 st1.Stack(4) 等价,因为构造函数是一个特殊函数,应该是先实例化出对象再进行调用,可以理解为 Stack st1(4)Stack st1; + st1.Stack(4); 等价。但是这种显示调用写法和 c 语言就没有差别了,所以这里就变成了 c++ 的一个特例:类名 + 对象(参数列表),调用带参的构造函数,或者:类名 + 对象,调用无参的构造函数。

问题:

为什么无参构造的时候不加括号,而带参构造的时候加括号呢?

因为如果无参构造加括号,就无法判断是函数声明还是调用无参构造,会出现编译器的报错。

🙊析构函数🙊

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

析构函数:

与构造函数功能相反,析构函数不是完成对对象本身的销毁,因为对象是创建在上的,函数结束栈帧销毁对象才被销毁。

局部对象销毁工作是由编译器完成的,对象在出了作用域就代表对象的生命周期结束,此时需要被销毁,销毁时会自动调用析构函数完成对象中资源的清理工作比,如动态开辟的空间需要释放需要调用析构函数。

💖 析构函数的特征

析构函数是特殊的成员函数,其特征如下:

1、 析构函数名是在类名前加上字符 ~。

2、无参数无返回值类型。

3、 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载

4、 对象生命周期结束时,C++ 编译系统系统自动调用析构函数。

💖 析构函数的使用方式及注意事项

析构函数使用方法示例如下面代码所示:

class Stack
{
public:
	Stack()
	{
		_a = nullptr;
		_size = _capacity = 0;
	}

	Stack(int n)
	{
		_a = (int*)malloc(sizeof(int) * n);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = n;
		_size = 0;
	}

	~Stack()
	{
		free(_a);
		_a = nullptr;
		_size = _capacity = 0;
	}
private:
	// 成员变量
	int* _a;
	int _size;
	int _capacity;
};


int main()
{
	Stack st;
	Stack st1(4);
	return 0;
} 

c 语言实现和 c++ 实现代码对比分析:

注意 return 相当于宣告函数生命周期已经结束,所以当执行到 return 语句的时候,会自动跳转到析构函数,将其进行释放。
在这里插入图片描述

💖 定义一个类并初始化

若我们想定义一个日期类,将如何进行初始化呢?请看如下代码:

class Date
{
public:
	//不带参的构造函数
	Date()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
	//带参的构造函数
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	//无参初始化
	Date d1();
	//带参初始化
	Date d2(2023, 2, 4);
}

此时这里有两个构成函数重载的构造函数,有了前面学习的缺省参数中全缺省的概念其实可以进一步合并,代码如下:

class Date
{
public:
	不带参的构造函数
	//Date()
	//{
	//	_year = 1;
	//	_month = 1;
	//	_day = 1;
	//}

	带参的构造函数
	//Date(int year, int month, int day)
	//{
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}

	//全缺省构造函数
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//打印函数
	void Print()
	{
		//cout << _year << "/" << _month << "/" << _day << endl;
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

可以看到全缺省构造函数使用起来十分方便,运行结果如下:

在这里插入图片描述
但是构造函数和全缺省构造函数可不可以同时存在呢?

我们知道在语法上二者是构成函数重载,可以同时存在,但是实际中二者是不能同时存在,调用的时候会报错,因为不传参的时候,可以调用无参也可以调用全缺省。


在这里插入图片描述

注意构造函数析构函数都是天选之子,如果我们不写,编译器也会自动生成,如果我们实现了,编译器就不会自动生成。

c++ 中规定,对象在实例化的时候,必须调用构造函数,看如下代码:

class Date
{
public:
  //构造函数
	Date(int year)
	{

	}
	//打印函数
	void Print()
	{
		//cout << _year << "/" << _month << "/" << _day << endl;
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
private:
	int _year;
	int _month; 
	int _day;
};

int main()
{
	//无参初始化
	Date d1;
	d1.Print();

	return 0;
}

我们自己定义了构造函数,编译器就不会生成构造函数,此时编译就会报错。

在这里插入图片描述
那么问题来了,编译器会默认生成构造函数,那还需不需要我们自己写呢?

下面来看看不写默认构造函数,程序的执行结果是什么,代码如下:

class Date
{
public:
	//打印函数
	void Print()
	{
		//cout << _year << "/" << _month << "/" << _day << endl;
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
private:
	int _year;
	int _month; 
	int _day;
};

int main()
{
	//无参初始化
	Date d1;
	d1.Print();
	return 0;
}

最后是随机值,执行结果如下:

在这里插入图片描述

💖 补充说明

💖 构造函数的补充说明

构造函数的一些说明:

1、关于编译器生成的默认成员函数,很多童鞋会有疑惑:不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?

2、对象调用了编译器生成的默认构造函数,但是 d 对象 _year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用??

解答:

C++把类型分成内置类型 (基本类型) 和 自定义类型。内置类型就是语言提供的数据类型,如:int/char…指针等,自定义类型就是我们使用 class / struct / union 等自己定义的类型。

💖 默认生成的构造函数特性

默认生成的构造函数有以下几个特性:

1、内置类型成员不做处理

2、自定义类型的成员,会去调用它的默认构造(不用传参数的那个构造:无参、全缺省、编译器生成)

那么当我们自定义一个数据类型的时候,不写构造函数会调用默认构造函数,自动对其进行初始化,代码如下:

class Stack
{
private:
	// 成员变量
	int* _a;
	int _size;
	int _capacity;
};

class MyQueue {
public:
	// 默认生成构造函数,对自定义类型成员,会调用他的默认构造函数
	// 默认生成析构函数,对自定义类型成员,会调用他的析构函数
	void push(int x) {
	}
	//....
	Stack _pushST;
	Stack _popST;
	int _size = 0;
};

int main()
{
	//Date d1;
	//d1.Print();
	MyQueue q;
	return 0;
}

这里 MyQueue 是自定义类型,会自动调构造函数和析构函数,所以 Stack _pushSTStack_popST 不需要手动调用析构函数,因为出了 q 的作用域,MyQueue 会调用系统自动生成的析构函数,自动生成的析构函数会对自定义类型的结构成员进行处理。执行结果如下:

在这里插入图片描述
注意:

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

如以下代码所示:

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

注意:

1、缺省值不是初始化,如果不写构造函数,内置类型就会使用缺省值进行初始化。

2、无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。

3、无参构造函数全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。默认构造函数就是不传参就可以调用的构造函数,一般建议每个类都提供一个默认构造函数。

如以下代码调用的是编译器自己生成的默认构造函数。

class Date
{
public:
	
	//打印函数
	void Print()
	{
		//cout << _year << "/" << _month << "/" << _day << endl;
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}


private:
	int _year;
	int _month; 
	int _day;
};

int main()
{
	Date d1;
	d1.Print();
	//MyQueue q;
	return 0;
}

如果显示写一个构造函数,编译器就不会生成默认构造函数,而导致报错

class Date
{
public:
	Date(int year)
	{	}
	//打印函数
	void Print()
	{
		//cout << _year << "/" << _month << "/" << _day << endl;
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}


private:
	int _year;
	int _month; 
	int _day;
};

int main()
{
	Date d1;
	d1.Print();
	//MyQueue q;
	return 0;
}

报错内容如下:


在这里插入图片描述
如果不用编译器自动生成的,自己写一个无参的默认构造函数或者全缺省的默认构造函数也可以

代码如下:

```cpp
class Date
{
public:
	//无参默认构造函数
	Date()
	{	}
	//全缺省默认构造函数
	Date(int year = 0)
	{ }
	//打印函数
	void Print()
	{
		//cout << _year << "/" << _month << "/" << _day << endl;
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}


private:
	int _year;
	int _month; 
	int _day;
};

int main()
{
	Date d1;
	d1.Print();
	//MyQueue q;
	return 0;
}

🙊拷贝构造函数🙊

💖 拷贝构造函数介绍

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

💖 拷贝构造函数举例刨析


看如下代码,如何用 d1 初始化 d2 呢?

int main()
{
	Date d1(2023, 2, 4);
	Date d2(d1);

	return 0;

构造函数可以重载,现在要用同类型的对象进行初始化,就需要用到拷贝构造函数。那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?看如下代码能否用这种方式对 d1 进行拷贝?


class Date
{
public:
	Date(int year = 1000, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(Date d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2023, 2, 4);
	Date d2(d1);

	return 0;
}

d1 将其传递给 d,然后左边成员通过 this 指针访问 d2,看似没有问题,但结果是编译器报错了

在这里插入图片描述

问题分析:

编译器会认为这样的拷贝构造会无穷递归,所以 c++ 规定一定要使用引用

Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

为什么不用引用会导致无穷递归呢?以下面的代码举例:

void Func1(Date d)
{

}

// 传引用传参
void Func2(Date& d)
{

}

int main()
{
	//创建对象 d1 并调用构造函数进行初始化
	Date d1(2023, 2, 3);
	Date d2(d1);

	Func1(d1);
	Func2(d1);

	return 0;
}

Func1 是传值传参,Func2 是传引用传参,因为我们知道,传值传参实际上是拷贝,就是开辟一块空间,将实参 d1 拷贝给形参 d,而传引用传参实际上形参是实参的别名,以前我们传的是内置类型,编译器知道怎么进行拷贝,但是自定义类型编译器不能随便拷贝,只能调用拷贝构造。

为什么呢?我们再举一个样例进行更加深入的说明:

如果是自定义的 Date 类,其里面的成员只有十几个字节,如果编译器还像之前那样直接把 d2 拷贝给 d1,由于日期类对象只有十几个字节,编译器可以进行浅拷贝将 d2 逐字节拷贝给 d1,此时看起来并没有什么问题。

但如果是 Stack 类,编译器如果按照逐字节的方式进行浅拷贝,会导致指向同一块空间的问题,而两个对象都会调用析构函数,所以会造成空间的重复释放的问题。图示如下:

在这里插入图片描述

基于上述问题,c++ 规定,对于自定义类型,编译器会调用拷贝构造函数来避免类似的问题,至于拷贝构造函数是深拷贝还是浅拷贝,需要程序员自己去判断完成。

我们通过以下程序进行验证:

class Date
{
public:
	Date(int year = 1000, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

private:
	int _year;
	int _month;
	int _day;
};

void Func1(Date d)
{

}

// 传引用传参
void Func2(Date& d)
{

}

int main()
{
	Date d1(2023, 2, 3);
	Date d2(d1);

	Func1(d1);
	Func2(d1);

	return 0;
}

给此处打一个断点

在这里插入图片描述
调试运行发现逐语句调试直接跳到了拷贝构造函数,并没有进入 func1 函数。

在这里插入图片描述
解释说明:

1、因为要调用 func1 就要先传参(如果不用引用就是传值传参),而传值传参就是对参数的拷贝,而前面讲到 c++ 规定自定义类型的拷贝不管是浅拷贝还是深拷贝,都要调用拷贝构造函数。而用引用传参就不需要调用拷贝构造函数。

2、这里 d1 为已经实例化的对象,而 d1 要拷贝给 d2,对 d2 进行实例化,由于 d2 是自定义类型的函数,所以需要调用拷贝构造函数,我们知道调用函数之前需要先传参,再为函数建立栈帧,由于这里使用传值传参,传值传参不是传实参本身,而是传实参的拷贝,这又是一个拷贝的过程,又需要调用拷贝构造函数,而再调用拷贝构造函数之前还需要先传参,再建立函数栈帧,传值传参又是一个拷贝的过程,又会调用拷贝构造函数,调用拷贝构造函数又会先传参。。。所以就形成了一个死循环。

在这里插入图片描述
而传引用的时候,dd1 的别名,this 就是 d2,所以就完成了 d1 传给 d2 的过程。

//Date d2(d1);
	Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

还需注意,拷贝构造还需加 const 进行修饰,避免 d 被修改,因为这里 dd1 的别名,d 是可读可写的,d1 变成只读的,这是权限的缩小,编译器允许权限的缩小而不允许权限的放大,所以一般拷贝构造需要加 const

  //Date d2(d1);
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

除此之外还有最后一点,就是拷贝构造有两种书写方式,使用哪一种都可以:

int main()
{
	Date d1(2023, 2, 3);
	Date d2(d1);
	Date d3 = d1;

	Func1(d1);
	Func2(d1);

	return 0;
}

🙊日期计算器实现🙊

我们根据以上介绍的相关内容实现一个日期计算器,计算 n 天以后的日期,类似加法进位的思想,天满了,进月位,月满了进年位,但是天和月的进位不规则,有些月是 30 天,有些月是 28 天,还需要考虑闰年的问题,那么根据这个思路,我们首先需要知道那个月都有多少天,才能进行进位判断。

💖 获取每个月的天数

按照上面的思路,我们这里实现一个函数获取每个月的天数,我们可以使用数组来进行处理, 这里给定了大小为 13 的数组,因为第 0 个位置的数可以不去使用,只需要将每个月的月数与数组的位置匹配即可。

注意:

这里需要特别考虑 2 月,因为闰年的 2 月天数是不固定的,需要单独进行判断,而闰年的规则:四年一闰、百年不闰、四百年为闰。根据这个规则写出以下获取月份天数的代码。

代码如下:

int GetMonthDay(int year, int month)
	{
		assert(month > 0 && month < 13);

		int monthArray[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))
		{
			return 29;
		}
		else
		{
			return monthArray[month];
		}
	}

💖 获取 x 天以后的日期

这里先考虑将 x 先加到 “ “ 位,如果天大于当前月的天数,就要进位,如果 “ ” 位等于 13 就将其置为 1,然后 “ ” 位加 1。而最后只需要返回 *this 即可,因为 this 是对象的指针,而 *this 是这个对象。

代码如下:

Date GetAfterXDay(int x)
	{
		_day += x;
		while (_day > GetMonthDay(_year, _month))
		{
			//进位
			_day -= GetMonthDay(_year, _month);
			++_month;
			if (_month == 12)
			{
				_year++;
				_month = 1;
			}
		}
	}

💖 代码实现以及分析

通过以上分析先写出实现的总代码:

class Date
{
public:
	//默认构造函数
	Date(int year = 1900, 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;

	}

	int GetMonthDay(int year, int month)
	{
		assert(month > 0 && month < 13);

		int monthArray[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))
		{
			return 29;
		}
		else
		{
			return monthArray[month];
		}
	}

	Date GetAfterXDay(int x)
	{
		_day += x;
		while (_day > GetMonthDay(_year, _month))
		{
			//进位
			_day -= GetMonthDay(_year, _month);
			++_month;
			if (_month == 12)
			{
				_year++;
				_month = 1;
			}
		}
		return *this;
	}

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

int main()
{
	Date d1(2023, 2, 3);
	Date d2 = d1.GetAfterXDay(100);
	d1.Print();
	d2.Print();
	
	// 实现一个函数,获取多少以后的一个日期

	return 0;
}

运行结果如下:
在这里插入图片描述
问题:
我们发现,d1d2 都被更改了,为什么呢?

因为 d1 调用 GetAfterXDay() 函数,其 _year_month_day 都是 d1 的变量。

那么怎么能不对 d1 进行更改呢?

应该先拷贝 d1 生成一个临时对象,而调用函数中,this 就是 d1 的地址,所以这里直接调用一个拷贝构造函数将 this 拷贝给 tmp,然后只改变 tmp 的成员,最后返回 tmp 就达到了效果。

代码如下:

class Date
{
public:
	//默认构造函数
	Date(int year = 1900, 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;

	}

	int GetMonthDay(int year, int month)
	{
		assert(month > 0 && month < 13);

		int monthArray[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))
		{
			return 29;
		}
		else
		{
			return monthArray[month];
		}
	}

 	Date GetAfterXDay(int x)
	{
		Date tmp = *this;
		tmp._day += x;
		while (tmp._day > GetMonthDay(tmp._year, tmp._month))
		{
			//进位
			tmp._day -= GetMonthDay(tmp._year, tmp._month);
			++tmp._month;
			if (tmp._month == 12)
			{
				tmp._year++;
				tmp._month = 1;
			}
		}
		return tmp;
	}

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

int main()
{
	Date d1(2023, 2, 3);
	Date d2 = d1.GetAfterXDay(100);
	d1.Print();
	d2.Print();
	
	// 实现一个函数,获取多少以后的一个日期

	return 0;
}

执行结果如下:

在这里插入图片描述
注意:

1、上述代码中 tmp 是一个局部对象,d2 = d1.GetAfterXDay(100) 类似于一个日期加一个天数,因为我们知道 j = i + 100,其 i 的值是不改变的。

2、同理 d1 的日期 + 100天结果给了 d2d1 也是不变的。注意这里不能使用引用返回,因为 tmp 出了作用域就销毁了,传值返回返回的是 tmp 的拷贝,调用拷贝构造函数拷贝出一个临时对象进行返回。

3、而类似我们也可以实现 += 的逻辑,而 += 这里就是改变自己。注意 += 的实现可以使用引用返回,因为 *this 出了作用域还存在,所以可以返回引用,而不用调用拷贝构造函数。

代码对比:

	// +
	Date Add(int x)
	{
		Date tmp = *this;
		tmp._day += x;
		while (tmp._day > GetMonthDay(tmp._year, tmp._month))
		{
			// 进位
			tmp._day -= GetMonthDay(tmp._year, tmp._month);
			++tmp._month;
			if (tmp._month == 13)
			{
				tmp._year++;
				tmp._month = 1;
			}
		}

		return tmp;
	}

	// +=
	Date& AddEqual(int x)
	{
		_day += x;
		while (_day > GetMonthDay(_year, _month))
		{
			// 进位
			_day -= GetMonthDay(_year, _month);
			++_month;
			if (_month == 13)
			{
				_year++;
				_month = 1;
			}
		}

		return *this;
	}

💖 总代码

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	// 拷贝构造
	// Date d2(d1);
	Date(const Date& d)
	{
		cout << "Date(Date& d)" << endl;

		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	int GetMonthDay(int year, int month)
	{
		assert(month > 0 && month < 13);

		int monthArray[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))
		{
			return 29;
		}
		else
		{
			return monthArray[month];
		}
	}
	// +
	Date Add(int x)
	{
		Date tmp = *this;
		tmp._day += x;
		while (tmp._day > GetMonthDay(tmp._year, tmp._month))
		{
			// 进位
			tmp._day -= GetMonthDay(tmp._year, tmp._month);
			++tmp._month;
			if (tmp._month == 13)
			{
				tmp._year++;
				tmp._month = 1;
			}
		}
		return tmp;
	}
	// +=
	Date& AddEqual(int x)
	{
		_day += x;
		while (_day > GetMonthDay(_year, _month))
		{
			// 进位
			_day -= GetMonthDay(_year, _month);
			++_month;
			if (_month == 13)
			{
				_year++;
				_month = 1;
			}
		}
		return *this;
	}
	
	void Print()
	{
		//cout << _year << "/" << _month << "/" << _day << endl;
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2023, 2, 3);
	Date d2 = d1.Add(100);
	Date d3 = d1.Add(150);

	d1.Print();
	d2.Print();
	d3.Print();

	d1.AddEqual(200);
	d1.Print();

	// 实现一个函数,获取多少以后的一个日期

	return 0;
}

💖💖💖💖💖至此构造函数、析构函数、拷贝构造函数都已经介绍完了,希望可以帮助到大家。💖💖💖💖💖💖💖

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值