当前位置: 首页 > article >正文

C++时之律者的代码掌控:日期类计算器万字详解

文章目录

  • 1.基本构造实现
    • 1.1 获取某年某月的天数
    • 1.2 构造函数
    • 1.3 拷贝构造函数
    • 1.4 打印函数
  • 2.运算符重载接口实现
    • 2.1日期比较
      • 2.1.1 <运算符重载
      • 2.1.2 ==运算符重载
      • 2.1.3 <=运算符重载
      • 2.1.4 >运算符重载
      • 2.1.5 >=运算符重载
      • 2.1.6 !=运算符重载
    • 2.2 日期与天数的计算
      • 2.2.1 日期+=天数
      • 2.2.2 日期+天数
      • 2.2.3 日期−=天数
      • 2.2.4 日期−天数
    • 2.3 日期的自增自减
      • 2.3.1 前置++
      • 2.3.2 后置++
      • 2.3.3 前置−−
      • 2.3.4 后置−−
    • 2.4 日期-日期
    • 2.5 日期的输入输出
      • 2.5.1 <<运算符重载
      • 2.5.2 >>运算符重载
  • 3.代码展示
    • 3.1 Date.h
    • 3.2 Date.c
  • 希望读者们多多三连支持
  • 小编会继续更新
  • 你们的鼓励就是我前进的动力!

学习完C++类和对象,我们可以实现一个说简单也不简单的日期计算功能,该功能涉及大量的运算符重载知识点及细节,可谓是细节重重,那么本篇将手把手教会你自己写一个日期计算器😎

在这里插入图片描述

传送门:日期计算器(网页版)

1.基本构造实现

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 };
	int day = days[month];
	if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
	{
		day += 1;
	}
	return day;
}

口诀:(四年一润,百年不润) 或者 四百年一润

1.2 构造函数

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

用于初始化创建的每一个实例

1.3 拷贝构造函数

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

通常拷贝构造函数用于用一个已经存在的对象初始化另一个对象,如Date d3(d1),等价于Date d3 = d1

但是像d1 = d2两个已经存在的对象之间的复制,调用的是=的运算符重载,而不是拷贝构造

具体实现解析可以看往期博客:

传送门:C++天使的灵动心跳代码:类和对象(中下)

1.4 打印函数

void Print()
{
	cout << _year << '-' << _month << '-' << _day << endl;
}

用于打印日期类

2.运算符重载接口实现

2.1日期比较

2.1.1 <运算符重载

bool Date::operator < (const Date& x)
{
	if (_year < x._year)
	{
		return true;
	}
	else if (_year == x._year && _month < x._month)
	{
		return true;
	}
	else if (_year == x._year && _month == x._month && _year < x._year)
	{
		return true;
	}

	return false;
}

实现日期小于的比较,我们要先思考日期的比较需要分别比较年月日,从年开始依次比较,一旦成立则说明小于成立;反之应该是大于,小于不成立

🔥值得注意的是:

  1. 传参时要特别注意,运算符有几个操作数,就传几个参数this指针也算在参数中
    所以这里默认<左边的对象是用this指针传过去了,右边的对象引用传参(加const避免被传的数值被修改,引用传参提高效率)
  2. 这里的运算符重载函数是成员函数

在这里插入图片描述

调试状态下,转到反汇编比较可以观察到,两种写法的底层代码是一样的,说明了d1 < d2会转换成d1.operator<(d2)

如果是非成员函数的情况下呢?

bool Date::operator < (const Date& x1, const Date& x2)
{
	if (x1._year < x2._year)
	{
		return true;
	}
	else if (x1._year == x2._year && x1._month < x2._month)
	{
		return true;
	}
	else if (x1._year == x2._year && x1._month == x2._month && x1._year < x2._year)
	{
		return true;
	}

	return false;
}

注意在类内要添加友元的声明,在类外定义运算符重载函数时,要传两个参数,原因在于非成员函数没有隐含的 this 指针来指向调用对象。不像成员函数,通过 this 指针隐式获取左侧对象,只需要显式接收右侧对象作为参数

在这里插入图片描述
同样转到反汇编比较,可以观察到非成员函数下,d1 < d2会转换成operator<(d1, d2),底层代码也清晰的指出要传几个参数

2.1.2 ==运算符重载

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

==的运算符重载很简单,直接返回即可,如果相等返回true反之则为false

2.1.3 <=运算符重载

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

显而易见,注意左边的数是由this指针隐式传递,满足小于或等于都能成立

2.1.4 >运算符重载

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

		return false;
	}

大于的运算符重载和小于的同理,修改符号即可

但是这样写实在是太麻烦了,根据上面写的==的运算符重载>=运算符重载<运算符重载,可以极大的简化和日期类比较有关的函数

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

<=的反面就是>,借助这一点能够减少很多不必要的代码

2.1.5 >=运算符重载

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

同理,借助刚写完的>运算符重载也能简单的写出>=运算符重载函数

2.1.6 !=运算符重载

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

==运算符重载取反即可

2.2 日期与天数的计算

2.2.1 日期+=天数

在这里插入图片描述
首先我们要明白日期+天数如何计算,那么经过举例演算可以发现,用进位的方式实现日期+天数,简单来说就是天满了往月进,月满了往年进,依赖于GetMonthDay函数处理每个月的天数,不断减掉天数直到合法为止

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

因为我们是在原本的日期上操作,所以是+=引用返回,而不是+,注意加上负数等同于减去正数,也可以用abs取绝对值

Date& Date::operator+=(int day)
{
	*this = *this + day;
	return *this;
}

+=还可以利用下面实现的日期+天数的运算来写

2.2.2 日期+天数

Date Date::operator+(int day)
{
	Date tmp(*this);

	tmp._day += day;
	while (tmp._day > GetMonthDay(tmp._year, tmp._month))
	{
		tmp._day -= GetMonthDay(tmp._year, tmp._month);
		++tmp._month;
		if (tmp._month == 13)
		{
			++_year;
			tmp._month = 1;
		}
	}
	return tmp;
}

那么真正的日期+天数,应该是不会修改原来的日期的,而是以另一个变量返回,所以这里就可以利用拷贝构造,将一个新的日期返回,注意不能引用返回,因为tmp是局部变量,出了作用域就被销毁了

Date Date::operator+(int day)
{
	Date tmp(*this);
	tmp += day;

	return tmp;
}

同理+也可以利用日期+=天数来运算,但是这两种简洁的写法不可能同时存在,因为至少要有一种是完整实现了的,那么就可以对两种组合进行比对

在这里插入图片描述
考虑到变量的拷贝以及创建,明显是创建完整的+=运算,然后据此写+运算更优,因为这个组合创建的对象更少减少对拷贝构造的调用

2.2.3 日期−=天数

在这里插入图片描述
举一反三,既然日期+天数是进位,那么日期-天数就是借位,每当被减成负数的时候,要注意是向上个月借天数不是本月的天数

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

+=的写法其实大差不差,注意减去负数就是加上正数

2.2.4 日期−天数

Date Date::operator-(int day)
{
	Date tmp(*this);
	*this -= day;
	return tmp;
}

利用-=运算减少不必要的算法,记得保存原先的日期

2.3 日期的自增自减

2.3.1 前置++

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

+=运算的前提下,日期也是能实现自增的,所以不用担心自增后会不会越过当月的天数,在+=已经处理好了

2.3.2 后置++

Date Date::operator++(int)
{
	Date tmp = *this;
	*this += 1;
	return tmp;
}

后置++需要提前保存值,因为是先使用再++

🔥值得注意的是:

  1. 前置++后置++虽然是运算符重载,但是形式上也构成函数重载
  2. 后置++增加这个int参数不是为了接受具体的值仅仅是占位,跟前置++构成重载
  3. int这个位置传任何值都可以,实际调用的时候前置++后置++可能分别为d1.operator++()d1.operator++(0)括号内的值是随机的

2.3.3 前置−−

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

−−的操作和++几乎是一样的,这里就不过多赘述了

2.3.4 后置−−

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

2.4 日期-日期

通常日期的计算我们一般是用来计算两个日期之前相差多少天,因此我们的运算符重载是否有意义决定了是否要创建这个重载,所以只考虑日期-日期日期+日期是没有意义的
在这里插入图片描述
根据前面的算法,我们也能很容易总结出计算方法,把年份大的的日期的月份天数假设成和一样的年份小的一样,然后两者作天数差的计算即可

其实还有更简单的思路,就是让年份小的日期一天一天去++

int Date::operator-(const Date& d)
{
	Date max = *this;
	Date min = d;
	int flag = 1;
	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}
	int n = 0;
	while (min != max)
	{
		++min;
		++n;
	}
	return n * flag;
}

这里最妙的是这个flag,能够刚好体现出计算的正负,虽然这种方式没有上面那种来的快,但是这种程度的差距对于计算机来说其实是可以忽略不计的

2.5 日期的输入输出

基本数据类型基于cout和cin的使用能够很方便的进行输入输出的操作,那么日期依托于运算符重载也能达到此类效果,coutcin分别属于iostram库里的ostreamistream

2.5.1 <<运算符重载

根据前面的经验,我们依旧把<<运算符重载放在成员函数里

void Date::operator<<(ostream& out)
{
	out << d._year << "年" << d._month << "月" << d._day;
}

可是当我们输出的时候就报错了,这是因为正常情况下cout<<d1会转化成d1.operator<<coutDate对象默认占用第一个参数,也就是做了左操作数,所以正确的写法应该是d1<<cout才能正常输出,但是这样太奇怪了,完全不符合我们的使用习惯

ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day;
	return out;
}

既然不能写成成员函数,那就在类外写成非成员函数,就可以带入两个参数,cout<<d1就会转化成operator<<(cout,d1),符合书写情况

🔥值得注意的是:

  1. cout从左往右连续输出,所以要返回ostream类型才能连续输出
  2. 类外访问私有变量使用友元函数就能解决
  3. ostream& out不能加const,因为流插入就是不断改变cout的过程

2.5.2 >>运算符重载

istream& operator>>(istream& in, const Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

同理,流输入也是同样的格式

3.代码展示

3.1 Date.h

#include <iostream>
#include <cstdbool>
using namespace std;

class Date
{
public:
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& out, const Date& d);
	// 计算每个月的天数
	int GetMonthDay(int year, int month)
	{
		static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30,31 };
		int day = days[month];
		if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
		{
			day += 1;
		}
		return day;
	}

	// 全缺省的构造函数
	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;
	}
 // d2(d1)

	//打印日期
	void Print()
	{
		cout << _year << '-' << _month << '-' << _day << endl;
	}

	bool operator < (const Date& x);
	bool operator > (const Date& x);
	bool operator >= (const Date& x);
	bool operator <= (const Date& x);
	bool operator==(const Date& x);
	bool operator!=(const Date& x);
	Date& operator+=(int day);
	Date operator+(int day);
	Date& operator-=(int day);
	Date operator-(int day);
	Date& operator++();
	Date operator++(int);
	Date& operator--();
	Date operator--(int);
	int operator-(const Date& d);
	
private:
	int _year;
	int _month;
	int _day;
};

3.2 Date.c

//1.<运算符重载
bool Date::operator < (const Date& x)
{
	if (_year < x._year)
	{
		return true;
	}
	else if (_year == x._year && _month < x._month)
	{
		return true;
	}
	else if (_year == x._year && _month == x._month && _year < x._year)
	{
		return true;
	}

	return false;
}

//2.>的运算符重载
bool Date::operator > (const Date& x)
{
	if (_year > x._year)
	{
		return true;
	}
	else if (_year == x._year && _month > x._month)
	{
		return true;
	}
	else if (_year == x._year && _month == x._month && _year > x._year)
	{
		return true;
	}

	return false;
}

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

//3.=的运算符重载
///*void operator=(const Date& d)
//{
//	_year = d._year;
//	_month = d._month;
//	_day = d._month;
//}*/
//Date& Date::operator=(const Date& d)
//{
//	if (this != &d)
//	{
//		_year = d._year;
//		_month = d._month;
//		_day = d._day;
//	}
//	return *this;
//}

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

//5.<=的运算符重载
bool Date::operator<=(const Date& x)
{
	return *this < x || *this == x;
}

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

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

//8.+=的运算符重载
Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		*this -= -day;
	}
	//*this = *this + day;
	//return *this;
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		++_month;
		if (_month == 13)
		{
			++_year;
			_month = 1;
		}
	}
	return *this;
}

//9.+的运算符重载
Date Date::operator+(int day)
{
	Date tmp(*this);
	tmp += day;

	return tmp;

	/*tmp._day += day;
	while (tmp._day > GetMonthDay(tmp._year, tmp._month))
	{
		tmp._day -= GetMonthDay(tmp._year, tmp._month);
		++tmp._month;
		if (tmp._month == 13)
		{
			++_year;
			tmp._month = 1;
		}
	}
	return tmp;*/
}

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

//11.后置++
Date Date::operator++(int)
{
	Date tmp = *this;
	*this += 1;
	return tmp;
}

//12.-=的运算符重载
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		*this += -day;
	}
	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			_month = 12;
			--_year;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

//13.-的运算符重载
Date Date::operator-(int day)
{
	Date tmp(*this);
	*this -= day;
	return tmp;
}

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

//15.后置--
Date Date::operator--(int)
{
	Date tmp = *this;
	*this -= 1;
	return tmp;
}

//16.日期-日期
int Date::operator-(const Date& d)
{
	Date max = *this;
	Date min = d;
	int flag = 1;
	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}
	int n = 0;
	while (min != max)
	{
		++min;
		++n;
	}
	return n * flag;
}

//17.输出日期
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day;
	return out;
}

//18.输入日期
istream& operator>>(istream& in, const Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

希望读者们多多三连支持

小编会继续更新

你们的鼓励就是我前进的动力!

请添加图片描述


http://www.kler.cn/a/553927.html

相关文章:

  • 仿 Sora 之形,借物理模拟之技绘视频之彩
  • 嵌入式面试高频面试题:嵌入式系统调试方法大全
  • Mysql基础语句
  • LeetCode 1299.将每个元素替换为右侧最大元素:倒序遍历,维护最大值,原地修改
  • rust笔记5-derive属性2
  • python和pycharm 和Anaconda的关系
  • Mysql各操作系统安装全详情
  • 主机的基本构成
  • 【Qt】缩略词
  • SpringCloud系列教程:微服务的未来 (五)枚举处理器、JSON处理器、分页插件实现
  • ceph HEALTH_WARN clock skew detected on mon.f, mon.o, mon.p, mon.q
  • 网络安全要学python 网络安全要学爬虫吗
  • vscode创建java web项目
  • 【强化学习】随机策略的策略梯度
  • Vue 3:基于按钮切换动态图片展示(附Demo)
  • 【Web前端开发精品课 HTML CSS JavaScript基础教程】第二十四章课后题答案
  • Centos开机自启动
  • 电路元器件知识:稳压二极管
  • Elasticsearch 混合搜索 - Hybrid Search
  • 【Java项目】基于SpringBoot的【校园台球厅人员与设备管理系统】