【c++】实现一个完善的日期类

【c++】实现一个完善的日期类_第1张图片

目录

一、本文目的:

二、具体内容:

三、代码实现:

1、 获得月天数--GetMonthDay

2、构造函数初始化--Date

3、拷贝构造-- Date

4、 运算符重载比较大小(<   <=   >   >=   ==   !=)

5、运算符重载实现+=  +与-=  -

6、赋值重载的实现

 7、(前置与后置)--和++的实现

 8、日期相减

(完整代码:)

四、运算符重载中的复用问题


一、本文目的:

以便于更好理解c++的的设计

二、具体内容:

1、构造(普通构造和拷贝构造)函数和析构函数的实现

2、运算符重载实现比较大小和理解高内聚和低耦合的关系

3、运算符重载实现+ - 等运算

知识点关键:懂得this指针的本质,构造函数如何构造,参数问题,引用做返回值问题,static修饰和inline内联函数作用问题等等

三、代码实现:

1、 获得月天数--GetMonthDay

static:因为这个函数需频繁调用,而用static只会在第一次调用此函数时初始化,再调用就不会初始化,提高了效率

inline:这个函数需频繁调用,不用inline的话会不断地压栈,导致栈开销变大,inline会在调用的地方展开,就没有栈开销了,故频繁调用的函数可以用inline修饰

//获得这一年这个月有多少天
inline int GetMonthDay(int year, int month)
{//加一个static就只会在第一次调用GetMonthDay时初始化一次
	static int monthDays[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)
		monthDays[2] = 29;//如果是2月且是闰年,则为29天

	return monthDays[month];
}

2、构造函数初始化--Date

Date(int year = 0, int month = 1, int day = 1)
	{
		//1、首先要检查你传入的范围是否有效
		if (year >= 0
			&& month >= 1 && month <= 12
			&& day >= 1 && day <= GetMonthDay(year,month))
		{
			_year = year;
			_month = month;
			_day = day;
		}
		else
		{
			cout << "非法日期" << endl;
		}
	}

3、拷贝构造-- Date

参数用&:防止重复的拷贝构造(基础知识)

const:防止修改d

//拷贝构造函数
	//Date d2(d1)
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

4、 运算符重载比较大小(<   <=   >   >=   ==   !=)

为什么要用运算符重载?

因为类是自定义类型,无法像内置类型一样直接比较大小,故利用运算符重载可实现自定义类型比较大小

 :先判断日期<的情况 -》①、年<年  ②、年相等,月<月  ③、年和月相等,日<日

如果上述情况不满足,就是>=

== :年和月和日都相等才行

<= :直接复用<==即可

 :直接复用<=即可

>= :直接复用<即可

!=  :直接复用==即可

//1、运算符重载实现比较大小

	//d1 < d2   -》   d1.operator<(&d1,d2);
	inline bool operator<(const Date& d)
	{//本质的this指针调用
	//bool operator<(Date* this, const Date& d)
		if (_year < d._year)
			return true;
		else if (_year == d._year && _month < d._month)
			return true;
		else if (_year == d._year && _month == d._month && _day < d._day)
			return true;

		return false;//上面的情况都不满足,肯定就是false
	}

	inline bool operator==(const Date& d)
	{//需多次调用的函数就可用inline(内联函数)
		return _year == d._year && _month == d._month && _day == d._day;
	}

	// d1 <= d2   -》  d1.operator<=(&d1, d2);
	//operator<=就类似于函数名,d1.operator相当于d1访问它的成员函数
	//&d1是为this指针传参

	bool operator<=(const Date& d)
	{
	//本质为bool operator<=(Date* this, const Date& d)
		return *this < d || *this == d; 
	//本质为d1 < d || d1 == d 而这里的比较因为是自定义类型
	//的还需要调用上面实现的两个运算符重载函数,即复用上面的来实现
	}

	bool operator>(const Date& d)
	{
		return !(*this <= d);//这里复用了实现的operator<=函数
	}

	bool operator>=(const Date& d)
	{
		return !(*this < d);
	}

	bool operator!=(const Date& d)
	{
		return !(*this == d);//复用了operator==函数
	}

5、运算符重载实现+=  +与-=  -

①+与+=有什么区别?

+不改变本身,改变的是另一变量

+=改变本身

比如int a = 0, b= 0;  a+=1 -> a = 1      b=a+1 ->  a并不改变

②、

+=:要先判断传入的天数是否合规,若<0,*this-=day,但自定义类型-=int类型的,无法直接减,故要先实现 operator-=,不能直接用_day-=day,因为日期相减不仅仅只是月天数相减那么简单,还要考虑年和月

+  :  要先拷贝构造一个临时对象ret,让其拷贝*this,返回临时对象ret就不要用引用了

-=:若减完天数<0,说明要往前借月来补这个天数,一定是从前一个月开始往前借,不能从这个月开始。

-  :  与+同理

//2、运算符重载实现+ -等运算
	Date operator+(int day)
	{
		//不是+=,不能改变传入的本身,所以需要先拷贝

		//1、直接实现
		//拷贝构造函数的调用
		//Date ret(*this);//因为是+不是+=,故用d1拷贝构造一个ret
		//ret._day += day;
		//while (ret._day > GetMonthDay(ret._year, ret._month))
		//{
		//	ret._day -= GetMonthDay(ret._year, ret._month);
		//	ret._month++;
		//	if (ret._month == 13)
		//	{
		//		ret._year++;
		//		ret._month = 1;
		//	}
		//}
		//return ret;//ret是局部变量,所以返回值不能用引用

		//2、复用实现(推荐)
		Date ret(*this);
		ret += day;//ret.operator+=(day)

		return ret;
	}

	//d1+=10
	Date& operator+=(int day)
	{//因为是+=,需要改变传入的本身,所以需先拷贝
		if (day < 0)
		{//如果传入的天数是负数
			return *this -= -day;
		}

		_day += day;
		if (_day > GetMonthDay(_year, _month))
		{
			_day -= GetMonthDay(_year, _month);
			_month++;
			if (_month == 13)
			{
				_year++;
				_month = 1;
			}
		}
		return *this;//返回的是本身,出了作用域还在,返回值可以用引用
	}

	//d1 - 10
	Date operator-(int day)
	{
		//1、直接实现
		/*Date ret(*this);
		ret._day -= _day;
		while (ret._day <= 0)
		{
			--ret._month;
			if (ret._month == 0)
			{
				--ret._year;
				ret._month = 12;
			}
			ret._day += GetMonthDay(ret._year, ret._month);
		}
		return ret;*/

		//2、复用实现(推荐)
		Date ret(*this);
		ret -= day;

		return ret;
	}

	//d1 -= 10
	Date& operator-=(int day)
	{
		if (day < 0)
		{
			return *this += -day;
		}

		_day -= day;
		while (_day <= 0)//不合法的day需继续处理
		{
			--_month;
			if (_month == 0)
			{
				--_year;
				_month = 12;
			}
			_day += GetMonthDay(_year, _month);
		}
		return *this;
	}

6、赋值重载的实现

赋值存在连续赋值,故operator=应返回Date类型

//赋值重载的实现
	//d3 = d1
	Date& operator=(const Date& d)
	{
		if (this != &d)//如果是两个相同的对象,则无需赋值操作
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}

 7、(前置与后置)--和++的实现

--和++分前置和后置的,如何区分前置与后置?

利用函数重载(--与++同理)

operator++()实现后置++,返回加之后的值

operator++(int)实现前置++,返回加之前的值

//++d1  -》  d1.operator++(&d1)
	Date& operator++()
	{
		*this += 1;
		
		return *this;//返回加之后的值,出了作用域还在
	}

	//d1++  -》  d1.operator++(&d1, 10) 任意传一个值即可
	Date operator++(int)//为了构成函数重载,加一个int,但这个int不会被使用
	{//写int 或者 int 变量名都可以
		Date tmp(*this);
		*this += 1;

		return tmp;//返回加之前的值
	}

	//--d1
	Date operator--()
	{
		*this -= 1;

		return *this;
	}

	//d1--
	Date operator--(int)
	{
		Date tmp(*this);
		*this -= 1;
		
		return tmp;
	}

 8、日期相减

日期相减,不用直接减,看日期小的那一个到日期大的那一个需要++多少次即可知道相差多少天数,日期类的相比用operator<相比即可,先假设其中前一个为大后一个为小,再比较,最终确定大的日期和小的日期 ,若与假设相反,说明日期相减会为负数,故用flag来判断是否为负数

【c++】实现一个完善的日期类_第2张图片

//d1 - d2 (日期相减:求相差多少天数)
	int operator-(const Date& d)
	{//思路:日期小的那一个一直++,直到与日期大的那个相等
	//期间总共加了多少次,即相差的天数,但要注意,如果是负数该怎么办
		int flag = 1;
		Date max = *this;//利用拷贝构造
		Date min = d;
		if (max < min)//operator<
		{
			max = d;//operator=
			min = *this;
			flag = -1;//如果传入的日期相减会是负数,则标志改为-1
		}
		int n = 0;
		while (min != max)//operator!=
		{
			++n;//operator++(),推荐调用前置++,因为相比后置少了一句代码
			++min;
		}
		return n * flag;
	}

(完整代码:)

①、声明和定义分离

分为三个文件:

Date.h:类的实现

Date.c:类的声明

Test.c:检测代码逻辑

②、this指针需要加上const

【c++】实现一个完善的日期类_第3张图片

错误分析:&d3是const Date*类型的,而this指针是Date*的,它想接收d3类型则必须为const,所以要用const 修饰this指针,使其为const Date* 类型的 

解释:本质上因为const对于非const的和const的都可以接收,因为权限的缩小是允许的,但若要改变成员变量,就不要const Date* this了(const修饰*this),不然就无法修改成员变量了。并且还有一种情况,如果一个成员函数使用了const修饰this,其他复用了这个成员函数的都要加上const修饰this指针,不然就是权限的放大了。

 结论:什么时候给成员函数加上const,只要成员函数中无需改变成员变量最好都加上const。只要成员函数中不直接或间接改变成员变量,建议都加const修饰this指针

完整代码如下(VS2022下运行):

Date.h:

#pragma once
#include
using namespace std;

class Date
{
public:
	int GetMonthDay(int year, int month)  const;
	Date(int year = 0, int month = 1, int day = 1);
	Date(const Date& d);
	inline bool operator<(const Date& d) const;//要么把内联去掉,要么内联函数就不要声明和定义相分离
	inline bool operator==(const Date& d) const;//内联函数要在调用的时候展开,展开就没有地址,不能定义和声明分离,否则链接时就找不到地址了
	bool operator<=(const Date& d) const;
	bool operator>(const Date& d) const;
	bool operator>=(const Date& d) const;
	bool operator!=(const Date& d) const;
	Date operator+(int day) const;
	Date& operator+=(int day);
	Date operator-(int day) const;
	Date& operator-=(int day);
	Date& operator=(const Date& d);
	Date& operator++();
	Date operator++(int);
	Date operator--();
	Date operator--(int);
	int operator-(const Date& d) const;
	void Print() const;
private:
	int _year;
	int _month;
	int _day;
};

Date.c:

#include"Date.h"

//获得这一年这个月有多少天
int Date :: GetMonthDay(int year, int month) const
{//加一个static就只会在第一次调用GetMonthDay时初始化一次
	static int monthDays[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)
		monthDays[2] = 29;//如果是2月且是闰年,则为29天

	return monthDays[month];
} 

//构造函数进行初始化
Date::Date(int year, int month, int day)//缺省参数声明和定义中只能留一个
{
	//1、首先要检查你传入的范围是否有效
	if (year >= 0
		&& month >= 1 && month <= 12
		&& day >= 1 && day <= GetMonthDay(year, month))
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << "非法日期" << endl;
	}
}

//拷贝构造函数
//Date d2(d1)
Date::Date(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}


//1、运算符重载实现比较大小
//d1 < d2   -》   d1.operator<(&d1,d2);
bool Date::operator<(const Date& d) const
{//本质的this指针调用
//bool operator<(Date* this, const Date& d)
	if (_year < d._year)
		return true;
	else if (_year == d._year && _month < d._month)
		return true;
	else if (_year == d._year && _month == d._month && _day < d._day)
		return true;

	return false;//上面的情况都不满足,肯定就是false
}

bool Date::operator==(const Date& d) const
{//需多次调用的函数就可用inline(内联函数)
	return _year == d._year && _month == d._month && _day == d._day;
}

// d1 <= d2   -》  d1.operator<=(&d1, d2);
//operator<=就类似于函数名,d1.operator相当于d1访问它的成员函数
//&d1是为this指针传参

bool Date::operator<=(const Date& d) const
{
	//本质为bool operator<=(Date* this, const Date& d)
	return *this < d || *this == d;
	//本质为d1 < d || d1 == d 而这里的比较因为是自定义类型
	//的还需要调用上面实现的两个运算符重载函数,即复用上面的来实现
}

bool Date::operator>(const Date& d) const
{
	return !(*this <= d);//这里复用了实现的operator<=函数
}

bool Date::operator>=(const Date& d) const
{
	return !(*this < d);
}

bool Date::operator!=(const Date& d) const
{
	return !(*this == d);//复用了operator==函数
}

//2、运算符重载实现+ -等运算
Date Date::operator+(int day) const
{
	//不是+=,不能改变传入的本身,所以需要先拷贝

	//1、直接实现
	//拷贝构造函数的调用
	//Date ret(*this);//因为是+不是+=,故用d1拷贝构造一个ret
	//ret._day += day;
	//while (ret._day > GetMonthDay(ret._year, ret._month))
	//{
	//	ret._day -= GetMonthDay(ret._year, ret._month);
	//	ret._month++;
	//	if (ret._month == 13)
	//	{
	//		ret._year++;
	//		ret._month = 1;
	//	}
	//}
	//return ret;//ret是局部变量,所以返回值不能用引用

	//2、复用实现(推荐)
	Date ret(*this);
	ret += day;//ret.operator+=(day)

	return ret;
}

//d1+=10
Date& Date::operator+=(int day)//this不加const,因为d1(成变)会改变
{//因为是+=,需要改变传入的本身,所以需先拷贝
	if (day < 0)
	{//如果传入的天数是负数
		return *this -= -day;
	}

	_day += day;
	if (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
			_year++;
			_month = 1;
		}
	}
	return *this;//返回的是本身,出了作用域还在,返回值可以用引用
}

//d1 - 10
Date Date::operator-(int day) const
{
	//1、直接实现
	/*Date ret(*this);
	ret._day -= _day;
	while (ret._day <= 0)
	{
		--ret._month;
		if (ret._month == 0)
		{
			--ret._year;
			ret._month = 12;
		}
		ret._day += GetMonthDay(ret._year, ret._month);
	}
	return ret;*/

	//2、复用实现(推荐)
	Date ret(*this);
	ret -= day;

	return ret;
}

//d1 -= 10
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		return *this += -day;
	}

	_day -= day;
	while (_day <= 0)//不合法的day需继续处理
	{
		--_month;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}



//赋值重载的实现
//d3 = d1
Date& Date::operator=(const Date& d)
{
	if (this != &d)//如果是两个相同的对象,则无需赋值操作
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	return *this;
}
//++d1  -》  d1.operator++(&d1)
Date& Date::operator++()//this不加const,因为调用了+=,d1(成变)被改变
{
	*this += 1;

	return *this;//返回加之后的值,出了作用域还在
}

//d1++  -》  d1.operator++(&d1, 10) 任意传一个值即可
Date Date::operator++(int)//为了构成函数重载,加一个int,但这个int不会被使用
{//写int 或者 int 变量名都可以
	Date tmp(*this);
	*this += 1;

	return tmp;//返回加之前的值
}

//--d1
Date Date::operator--()
{
	*this -= 1;

	return *this;
}

//d1--
Date Date::operator--(int)
{
	Date tmp(*this);
	*this -= 1;

	return tmp;
}

//d1 - d2 (日期相减:求相差多少天数)
int Date::operator-(const Date& d) const
{//思路:日期小的那一个一直++,直到与日期大的那个相等
//期间总共加了多少次,即相差的天数,但要注意,如果是负数该怎么办
	int flag = 1;
	Date max = *this;//利用拷贝构造
	Date min = d;
	if (max < min)//operator<
	{
		max = d;//operator=
		min = *this;
		flag = -1;//如果传入的日期相减会是负数,则标志改为-1
	}
	int n = 0;
	while (min != max)//operator!=
	{
		++n;//operator++(),推荐调用前置++,因为相比后置少了一句代码
		++min;
	}
	return n * flag;
}

//打印年月日
void Date::Print() const
{
	cout << _year << "-" << _month << "-" << _day << endl;
}

Test.c:

#define _CRT_SECURE_NO_WARNINGS 1

#include"Date.h"

int main()
{
	Date d1(2023, 7, 27);
	d1.Print();

	Date d2(2023, 7, 28);
	d1.Print();
	//运算符重载比较大小
	cout << (d1 < d2) << endl;//因为<<优先级大于<,所以加()
	cout << (d1 > d2) << endl;
	cout << (d1 == d2) << endl;
	cout << (d1 != d2) << endl;
	cout << (d1 <= d2) << endl;
	cout << (d1 >= d2) << endl;

	//是否要重载一个运算符,要看这个运算符是否对这个类的对象有意义
	//比如日期-日期、日期-天数等是有意义的
	//利用运算符重载实现计算
	Date d4 = d1 + 100;
	d4.Print();
	d1 + 10;

	d4 = d2 = d1;//连续赋值
	d2.Print();
	d4.Print();

	cout << "d2 - d1:" << d2 - d1 << endl;

	system("pause");
	return 0;
}

运行结果如下: 

【c++】实现一个完善的日期类_第4张图片

四、运算符重载中的复用问题

我们以后工作里面用的复用用的多吗?耦合度是否大?

复用是内聚高,软件工程提倡的是低耦合,高内聚

【c++】实现一个完善的日期类_第5张图片

并且利用复用当成员变量(成员属性)变多时,只需改变那个被复用的,其他复用被复用的成员函数时就无需修改了,效率高,如果不复用,那每个成员函数可能都需要改变。

你可能感兴趣的:(【c++基础】,c++,开发语言)