@TOC


🍖前言

通过类和对象的基本学习,我们可以实现一个完整的日期类。本文探讨日期类如何实现。


Tip

关于内联函数:在类内定义的函数,如果代码量少的话,编译器会酌将其转换成内联函数,这样会在调用的地方直接展开,能够提高效率。

在这个日期类中,提高的效率不是很大,所以在本文将日期类的成员函数的声明和定义分离了。如果想写成内联,需要直接在类里面定义。(内联的声明和定义不能分离)

日期类的六大默认成员函数:

日期类的成员变量如下:

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

一、🍖构造函数

由于日期类的成员变量只有年月日,且无其他的动态资源的申请,所以构造函数可直接实现:

// 获取某年某月的天数
int Date::GetMonthDay(int year, int month)
{
	if (year < 0 || year>9999)
	{
		perror("error year!");
		exit(-1);
	}
	if (month < 0 || month > 12)
	{
		perror("error month!");
		exit(-1);
	}
	static int DaysGet[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 DaysGet[month];
	}
}

        //构造函数
Date::Date(int year = 1900, int month = 1, int day = 1)
	{
		if (year < 0 || year>9999 )
		{
			perror("error year!");
			exit(-1);
		}
		if (month < 0 || month > 12)
		{
			perror("error month!");
			exit(-1);
		}
		if (day < 0 || day > GetMonthDay(year, month))
		{
			perror("error day!");
			exit(-1);
		}
		_year = year;
		_month = month;
		_day = day;
	}

Tip:

GetMonthDay() 函数是获取某年某月的天数,比如2023年5月,返回31天。 细节点1. GteMonthDay的数组写成static是为了提高效率,因为GetMonthDay函数需要不断地调用,所以这个数组只需要创建一次即可,不需要重复创建。

细节点2. 在判断闰年部分,最开始先要判断月份是否为2月,(因为闰年只跟2月有关)再判断是否为闰年即可提高效率。

二、🍖拷贝构造函数

日期类不需要动态申请空间资源,所以这里的拷贝构造函数可写可不写,因为如果我们不写,编译器生成的拷贝构造函数会自己完成内置类型的浅拷贝。

Date::Date(const Date& d)
{
	this->_year = d._year;
	this->_month = d._month;
	this->_day = d._day;
}

三、🍖析构函数

由于日期类没有动态资源空间的申请,不需要实现析构函数。

四、🍖日期类的赋值运算符重载

Date& Date::operator=(const Date& d)
{
	//防止自己给自己赋值,可以判断一下
	//注意&d的&是取地址的意思
	if (this != &d)
	{
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;
	}
	return *this;
}

Tip: 1.赋值运算符重载的返回类型是Date&,为了达到连续赋值的效果,且能提高效率。 2.参数设置为const Date&,是因为赋值前后右值未发生改变,使用const修饰更安全,且传引用可以提高效率,减少调用构造函数或拷贝构造函数的次数。

五、🍖取地址运算符重载

返回对象的地址即可。(在日期类没有特别大的意义)

const Date* Date::operator&()
{
	return this;
}

六、🍖const取地址运算符重载

返回对象的地址即可。(在日期类没有特别大的意义)

const Date* Date::operator&() const
{
	return this;
}

Tip: 取地址运算符重载和const取地址运算符重载区别在于this指针是否被const修饰, 一个是 Datethis, 一个是 const Datethis,它们构成函数重载。

日期类的关系操作符重载

在日期类中,只要实现

1.>运算符重载和==运算符重载

或者

2.<运算符重载和==运算符重载

即可完成其他的关系运算符重载。

一、🍖>运算符重载

1.先判断年是否大于 2.如果年大于,再判断是否月大于 3.如果年和月都大于,再判断日是否大于

以上三种情况均返回真,其余情况返回假

bool Date::operator>(const Date& d)
{
	if (_year > d._year)
	{
		return true;
	}
	if (_year == d._year && (_month > d._month))
	{
		return true;
	}
	if ( (_year == d._year) && (_month > d._month) && (_day > d._day) )
	{
		return true;
	}

	return false;
}

二、🍖==运算符重载

bool Date::operator==(const Date& d)
{
	return _year == d._year && _month == d._month && _day == d._day;
}

三、🍖>=运算符重载

此时可以复用 > 运算符重载和 == 运算符重载了。

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

四、🍖<运算符重载

<运算符重载又可以复用>=运算符重载,只需要取反方向即可。

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

五、🍖<=运算符重载

<=运算符重载又可以复用>运算符重载,只需要取反方向即可。

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

六、🍖!=运算符重载

!=运算符重载又可以复用 ==运算符重载,只需要取反方向即可。

// !=运算符重载
bool Date::operator != (const Date& d)
{
	return !(*this == d);
}

日期类和天数的操作

一、🍖日期+=天数

日期+=天数,就是让日期本身变了。 比如:2023年1月1日+10天后,原来的日期变成了2023年1月11日。 返回原来的对象,但是对象的成员变量已被修改。

具体实现: 给定一个天数,首先让对象的天数加上,如果对象的天数大于当月的天数,就让对象的天数给减掉当月的天数,然后让月份进一位,然后判断月份是否大于12,如果月份大于12,让年进一位,同时让月份回到1月,如此循环。

最后返回*this,即返回自身。

//+=就是让自己变了
Date& Date::operator+=(int day)
{
	//如果天数<0,意思就是
	if (day < 0)
	{
		return *this -= -day;
	}
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month += 1;
		if (_month > 12)
		{
			_year += 1;
			_month = 1;
		}
	}
	return *this;
}

Tip: 需要注意的点:如果日期小于0,相当于+=一个小于0的数,即 -= 一个大于0的数,可以复用下面实现的-=运算符重载。

二、🍖日期+天数

直接复用+=即可。 但是要注意的是,日期+天数返回的是一个新的对象,不是返回原来的对象。 所以需要在函数内部新建一个对象,该新的对象调用它拷贝构造函数拷贝原来的对象,+=天数后返回新对象。

Date Date::operator+(int day)
{
	//可以复用 +=
	Date tmp(*this);
	tmp.operator+=(day);

	return tmp;
}

三、🍖日期-=天数

整体实现方法与+=类似,但需要注意的是: -=时,天数应该与上一个月的天数进行比较,因为-的是上个月的天数。

首先将对象的日期减掉天数,如果对象日期小于0,那就需要从上一个月来借用天数了。 回到上一个月先需要--_month,才可以回到上个月,--对象的月份后,如果月份小于1了,就需要从年借一年,--对象的年,然后让月份 = 12,如此循环。

Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		return *this += -day;
	}
	_day -= day;
	while (_day < 1)
	{
		--_month;
		if (_month < 1)
		{
			--_year;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

Tip: 需要注意的点:如果日期小于0,相当于-=一个小于0的数,即 +=一个大于0的数,可以复用+=运算符重载。

四、🍖日期-天数

复用-=即可,与日期+天数类似,返回的是一个新的对象,不改变原对象。

// 日期-天数
Date Date::operator-(int day)
{
	//可以复用 -=
	Date tmp(*this);
	tmp.operator-=(day);

	return tmp;
}

五、🍖日期-日期

日期-日期的思想在于,先假设被减数是大的日期,减数是小的日期,如果假设不成立,那就交换一下顺序。

如果是小-大,那么返回日期的负数,表示回退多少多少天。

计算天数方法:从小的日期开始,不断累加,直到加到和大的日期相等即可。

在此期间会调用++运算符重载,在下面会实现。

//日期-日期
int Date::operator-(const Date& d)
{
	//小-大返回负数,大-小返回正数
	Date max = *this;
	Date min = d;
	int flag = 1;
	//如果假设错误,交换一下
	if (max < min)
	{
		max = d;
		min = *this;
		flag = -1;
	}
	int days = 0;//计算天数差
	while (min != max) //!=更高效, <还要比年月日
	{
		++days;
		++min;
	}
	return  flag * days;
}

日期类++和--操作

一、🍖前置++

复用+=运算符重载即可。

Date& Date::operator++()
{
	//this->operator+=(1);
	*this += 1;
	return *this;
}

二、🍖后置++

注意前置++和后置++是构成函数重载的,为了区分,我们给后置++一个参数类型,该参数不需要用到,所以给不给参数名都可以。

一般来说,尽量使用前置++,因为后置++返回的是++之前的对象,需要新建立一个对象,返回再返回该对象,新建立对象会调用拷贝构造函数,返回该对象又会给临时空间调用拷贝构造函数,效率较低。

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

	tmp += 1;
	return tmp;
}

三、🍖前置--

--操作的解释与++操作相似。

// 前置--
Date& Date::operator--()
{
	//this->operator-=(1);
	*this -= 1;

	return *this;
}

四、🍖后置--

--操作的解释与++操作相似。

Date Date::operator--(int)
{
	Date tmp(*this);

	//tmp.operator-=(1);
	tmp -= 1;

	return tmp;
}

整体代码(Date.h 和Date.cpp)

Date.h文件

class Date
{
	//成员函数如果不放在public里面,默认就是私有的
public:

	// 全缺省的构造函数
	Date(int year = 1900, int month = 1, int day = 1);

	// 获取某年某月的天数
	int GetMonthDay(int year, int month);

	void Print()
	{
		printf("%d %d %d\n", _year, _month, _day);
	}
	//本质上是:
	//void Print(Date* const this)
	
	//d1.Print()   本质上是:
	//d1.Print(&d1);


	// 拷贝构造函数
  // d2(d1)
//必须传引用,如果传值调用,会导致无限递归
	Date(const Date& d);

	//取地址运算符重载
	const Date* operator&();

	//const取地址运算符重载
	const Date* operator&() const;

	//函数声明
	//赋值运算符重载
	Date& operator=(const Date& d);
	Date& operator+=(int day);
	Date operator+(int day);
	Date operator-(int day);
	Date& operator-=(int day);
	//前置++
	Date& operator++();
	//后置++
	Date operator++(int);
	//后置--
	Date operator--(int);
	//前置--
	Date& operator--();
	bool operator>(const Date& d);
	bool operator==(const Date& d);
	bool operator >= (const Date& d);
	bool operator < (const Date& d);
	bool operator <= (const Date& d);
	bool operator != (const Date& d);
	// 日期-日期 返回天数
	int operator-(const Date& d);
	//如果不设置成友元函数
	// d << cout ,特别别扭,因为this指针是指向d的,传参穿的是cout流插入符号

	ostream& operator<<(ostream& out);

	//这两个构成重载
	const Date* operator&();
	const Date* operator&() const;


private:

	int _year;

	int _month;

	int _day;

};

Date.cpp文件

#define _CRT_SECURE_NO_WARNINGS 1

#include"Date.h"

// 获取某年某月的天数
int Date::GetMonthDay(int year, int month)
{
	if (year < 0 || year>9999)
	{
		perror("error year!");
		exit(-1);
	}
	if (month < 0 || month > 12)
	{
		perror("error month!");
		exit(-1);
	}
	static int DaysGet[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 DaysGet[month];
	}
}

//构造函数
Date::Date(int year = 1900, int month = 1, int day = 1)
{
	if (year < 0 || year>9999)
	{
		perror("error year!");
		exit(-1);
	}
	if (month < 0 || month > 12)
	{
		perror("error month!");
		exit(-1);
	}
	if (day < 0 || day > GetMonthDay(year, month))
	{
		perror("error day!");
		exit(-1);
	}
	_year = year;
	_month = month;
	_day = day;
}


//拷贝构造
Date::Date(const Date& d)
{
	this->_year = d._year;
	this->_month = d._month;
	this->_day = d._day;
}

// 析构函数
//日期类不需要析构
//~Date();




// 赋值运算符重载
Date& Date::operator=(const Date& d)
{
	//防止自己给自己赋值,可以判断一下
	//注意&d的&是取地址的意思
	if (this != &d)
	{
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;
	}
	return *this;
}


//取地址运算符重载
const Date* Date::operator&()
{
	return this;
}

//const取地址运算符重载
const Date* Date::operator&() const
{
	return this;
}


// 日期+=天数
//+=就是让自己变了
Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		return *this -= -day;
	}
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month += 1;
		if (_month > 12)
		{
			_year += 1;
			_month = 1;
		}
	}
	return *this;
}

// 日期+天数
Date Date::operator+(int day)
{
	//可以复用 +=
	Date tmp(*this);
	tmp.operator+=(day);

	return tmp;
}

// 日期-=天数
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		return *this += -day;
	}
	_day -= day;
	while (_day < 1)
	{
		--_month;
		if (_month < 1)
		{
			--_year;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

// 日期-天数
Date Date::operator-(int day)
{
	//可以复用 -=
	Date tmp(*this);
	tmp.operator-=(day);

	return tmp;
}

//日期-日期
int Date::operator-(const Date& d)
{
	//小-大返回负数,大-小返回正数
	Date max = *this;
	Date min = d;
	int flag = 1;
	//如果假设错误,交换一下
	if (max < min)
	{
		max = d;
		min = *this;
		flag = -1;
	}
	int days = 0;//计算天数差
	while (min != max) //!=更高效, <还要比年月日
	{
		++days;
		++min;
	}
	return  flag * days;
}

// >运算符重载
//实现一个大于和一个等于就可以完成其他所有的操作了
bool Date::operator>(const Date& d)
{
	if (_year > d._year)
	{
		return true;
	}
	if (_year == d._year && (_month > d._month))
	{
		return true;
	}
	if (_year == d._year && (_month > d._month) && _day > d._day)
	{
		return true;
	}

	return false;
}

// ==运算符重载
bool Date::operator==(const Date& d)
{
	return _year == d._year && _month == d._month && _day == d._day;
}

// >=运算符重载
bool Date::operator >= (const Date& d)
{
	return *this > d || *this == d;
}

// <运算符重载
bool Date::operator < (const Date& d)
{
	return !(*this >= d);
}

// <=运算符重载
bool Date::operator <= (const Date& d)
{
	return !(*this > d);
}

// !=运算符重载
bool Date::operator != (const Date& d)
{
	return !(*this == d);
}


// 前置++
Date& Date::operator++()
{
	//this->operator+=(1);
	*this += 1;
	return *this;
}


// 后置++
Date Date::operator++(int)
{
	Date tmp(*this);
	//this->operator+=(1);

	tmp += 1;
	return tmp;
}

// 前置--
Date& Date::operator--()
{
	//this->operator-=(1);
	*this -= 1;

	return *this;
}

// 后置--
Date Date::operator--(int)
{
	Date tmp(*this);

	//tmp.operator-=(1);
	tmp -= 1;

	return tmp;
}








//友元函数
// Date d 最好加const
//ostream& operator<<(ostream& out, const Date d)
//{
//	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
//	return out;
//}

// d << cout
//不能加const,加了报错,不能限制流的改变
ostream& Date::operator<<(ostream& out)
{
	out << _year << "年" << _month << "月" << _day << "日" << endl;
	return out;
}

🍖总结

本文实现了一个具体的日期类及其功能。