类和对象实操之【日期类】

✨个人主页: Yohifo
🎉所属专栏: C++修行之路
🎊每篇一句: 图片来源

  • The pessimist complains about the wind; the optimist expects it to change; the realist adjusts the sails.
    • 悲观主义者抱怨风;乐观主义者期望它改变;现实主义者调整风帆。

日期类

🗓️前言

在学完类和对象相关知识后,需要一个程序来供我们练习、巩固知识点,日期类就是我们练习的首选程序,日期类实现简单且功能丰富,相信在完整地将日期类实现后,能对类和对象有更好的掌握及更深的理解

日历

🗓️正文

为了更符合工程标准,这里采用三个文件的方式实现程序

用于声明类和方法的 .h 头文件

Date.h

用于实现类和方法的 .cpp 源文件

Date.cpp

用于测试功能的 .cpp 源文件

test.cpp

工程框架

📆类的定义

先简单定义一下每个类中都有的默认成员函数

//当前位于文件 Date.h 中
#pragma once
#include<iostream>
using std::cout;	//采用部分展开的方式
using std::cin;

//采用命名空间
namespace Yohifo
{
	class Date
	{
	public:
		//构造函数,频繁使用且短小的代码直接在类的声明中实现,成为内联函数
		Date(int year = 2023, int month = 2, int day = 11)
			:_year(year)
			, _month(month)
			, _day(day)
		{}

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

		//赋值重载函数
		Date& operator=(const Date& d)
		{
			if (this == &d)
				return *this;

			_year = d._year;
			_month = d._month;
			_day = d._day;
			return *this;
		}

		//析构函数
		~Date()
		{
			_year = 1970;
			_month = 2;
			_day = 11;
		}

	private:
		int _year;	//年、月、日
		int _month;
		int _day;
	};
}

📅合法性检验

首先编写第一个函数:合法性检验

检验标准

注意:

#include"Date.h"

using namespace Yohifo;	//全局展开命名空间

//合法性检验
bool Date::check() const
{
	//年不能为0年
	if (_year == 0)
		return false;

	//月份区间 [1, 12]
	if (_month < 1 || _month > 12)
		return false;

	//天数要合理
	// getMonthDay 函数后续实现
	if (_day < 1 || _day > getMonthDay())
		return false;

	return true;
}

📅判断闰年

闰年二月多一天,因此需要特殊处理

闰年判断技巧: 四年一闰且百年不闰 或者 四百年一闰

//闰年判断
bool Date::checkLeapYear() const
{
	//按照技巧判断
	if (((_year % 4 == 0) && (_year % 100 != 0)) || (_year % 400 == 0))
		return true;
	else
		return false;
}

📅获取年份天数

闰年多一天,为 366 ,非闰年为 365,判断返回即可

//获取年份天数
int Date::getYearDay() const
{
	//复用代码
	return (checkLeapYear() ? 366 : 365);
}

📅获取月份天数

根据当前年份和月份,判断当月有多少天

注意: 闰年的二月需要特殊处理

//获取月份天数
int Date::getMonthDay() const
{
	//非闰年情况下每个月天数,其中下标代表月份
	int arr[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

	//如果为2月,且为闰年,直接返回 2月+1天
	if (_month == 2 && checkLeapYear())
		return arr[_month] + 1;
	else
		return arr[_month];
}

📆运算符重载

前面学习了 operator 运算符重载,现在正好可以拿来练练手

📅判断等于

两个日期相等的前提是 都相等

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

📅判断小于

注意: 我们的运算顺序都是 左操作数右操作数,其中隐含的 this 指针默认为 左操作数

*this 小于 d 的逻辑

//判断小于
bool Date::operator<(const Date& d) const
{
	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;
	else
		return false;
}

📅复用至所有判断

善用代码复用,有了等于和小于,我们可以直接写出所有判断

//判断不等于
bool Date::operator!=(const Date& d) const
{
	return !(*this == d);	//等于,取反为不等于
}

//判断小于等于
bool Date::operator<=(const Date& d) const
{
	//小于、等于成立一个即可
	return ((*this < d) || (*this == d));
}

//判断大于
bool Date::operator>(const Date& d) const
{
	//即不小于,也不等于
	return (!(*this < d) && !(*this == d));
}

//判断大于等于
bool Date::operator>=(const Date& d) const
{
	//大于或等于
	return ((*this > d) || (*this == d));
}

📅重载流插入、提取

coutcin 只能输出、输出内置类型,但如果我们对它进行改造一下,就能直接输出我们的自定义类型

注意:

此时可以利用合法性检验了

实现 operator>> 时,右操作数不能用 const 修饰

//在 Date.h 内
//新增几个局部展开
using std::ostream;
using std::istream;
using std::endl;

namespace Yohifo
{
	class Date
	{
		//声明为类的友元函数
		friend std::ostream& operator<<(std::ostream& out, const Date& d2);
		friend std::istream& operator>>(std::istream& in, Date& d2);	//注意
		
		//……
	};

	//直接定义在头文件中,成为内联函数
	inline ostream& operator<<(ostream& out, const Date& d)
	{
		//此时需要检验日期合法性
		if (Date(d).check() == false)
		{
			out << "警告,当前日期非法!" << endl;
			out << "后续操作将会受到限制" << endl;
		}

		out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
		return out;
	}

	inline istream& operator>>(istream& in, Date& d)
	{
		Date tmp;

flag:
		cout << "请入日期,格式为:年 月 日" << endl;
		in >> tmp._year >> tmp._month >> tmp._day;

		//如果输入日期非法,就重新输入
		if (Date(tmp).check() == false)
		{
			cout << "警告,当前日期非法!" << endl;
			cout << "日期输入失败,请尝试重新输入!" << endl;
			goto flag;
		}

		cout << "输入成功!" << endl;

		return in;
	}
}

有了这两个运算符重载后,我们就可以直接对自定义类型(日期类对象)直接进行输入输出操作了

Date d1;
cin >> d1;	//对自定义类型的输入
cout << d1;	//对自定义类型的输出

📆日期+=天数

下面涉及两个重要算法

这里把 日期 += 天数 介绍清楚了,日期 -= 天数 就很好写了,就是倒着走

📅核心思想

注:此时实现的是 日期+=天数

进位思想:天数满了后进位到月份上,月份满后进位至年份上

进位

注意:

📅代码实现

//日期+=天数
Date& Date::operator+=(const int val)
{
	if (check() == false)
	{
		cout << "警告,当前日期非法,无法进行操作" << endl;
		return *this;
	}
	
	//判断 val,避免恶意操作,如 d1 += -100
	if (val < 0)
	{
		//此时需要调用 -=
		*this -= (-val);
		return *this;
	}

	//因为是 += 不需要创建临时对象

	//首先把天数全部加至 _day 上
	_day += val;

	//获取当前月份天数
	int monthDay = getMonthDay();

	//判断进位,直至 _day <= monthDay
	while (_day > monthDay)
	{
		//此时大于,先把多余的天数减掉
		_day -= monthDay;

		//此时进位一个月
		++_month;

		//判断月份是否大于 12
		if (_month > 12)
		{
			//此时需要进年
			++_year;

			//月份变为1月
			_month = 1;

			//判断是否为0年
			if (_year == 0)
				_year = 1;	//调整
		}

		//重新获取月份天数
		monthDay = getMonthDay();
	}

	//返回 *this 本身
	return *this;
}

有了这个函数后,我们就可以根据当前日期推算 N 天后的日期
运行结果

日期+天数 可以直接复用上面的代码,而 日期-=天数 将逻辑反过来就行了,这里不展示代码了,完整代码在文末的 gitee 仓库中

📆日期-日期

日期+日期无意义,但日期-日期有,可以计算两日期差值

日期相减有两种情况:

具体实现时也很好处理,直接用一个 flag 就行了

📅核心思想

先不管左右操作数大小,我们先找出较大操作数与较小操作数

通过较小操作数逐渐逼近较大操作数,其中经过的天数就是差值

步骤:

除了这种方法外,我们还可以直接一天一天的加,直到相等,当然这种效率较低

📅代码实现

//日期 - 日期
const int Date::operator-(const Date& d) const
{
	if (check() == false || d.check() == false)
	{
		cout << "警告,当前日期非法,无法进行操作!默认返回 0" << endl;
		return 0;
	}

	//假设右操作数为较大值
	Date max(d);
	Date min(*this);
	int flag = 1;

	//判断
	if (min > max)
	{
		max = *this;
		min = d;
		flag = -1;
	}

	//小的向大的靠近
	int daySum = 0;

	//考虑天
	while (min._day != max._day)
	{
		min += 1;
		daySum++;
	}

	//考虑月
	while (min._month != max._month)
	{
		daySum += min.getMonthDay();
		min += min.getMonthDay();
	}

	//考虑年
	while (min._year != max._year)
	{
		daySum += min.getYearDay();
		min._year++;
	}

	return daySum * flag;
}

这种方法(同轴转动)将会带来一定的性能提升(相对逐天相加来说)

方法相差 1k 年相差 1w 年相差 10w 年
同轴转动耗时 0 ms耗时 0 ms耗时 2 ms
逐天相加耗时 28 ms耗时 297 ms耗时 3142 ms

注:实际差异与电脑性能有关

📆自加、自减操作

自加操作实现很简单,不过需要注意编译器是如何区分两者的

占位参数

📅前置

前置直接复用前面 += 的代码

前置操作是先进行自加或自减,再返回

//前置++
Date& Date::operator++()
{
	//直接复用
	*this += 1;
	return *this;
}

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

📅后置

此时需要借助 占位参数,当启用时,编译器会自动传参,并自动区分,占位参数 类型为 int

后置操作是先记录值,再进行自加或自减,返回之前记录的值

//后置++
const Date Date::operator++(int)
{
	//借助临时变量
	Date tmp(*this);

	*this += 1;

	return tmp;
}

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

	*this -= 1;

	return tmp;
}

特别注意: 对于自定义类型来说,在进行自加、自减操作时,最好采用前置,因为后置会发生拷贝构造行为,造成资源浪费

📆程序源码

完整的代码在这里 Gitee
动图

🗓️总结

以上就是关于日期类实现的全部内容了,涉及到了前面学的大部分知识,希望大家在看完后能把它独立敲一遍,加深理解

如果你觉得本文写的还不错的话,可以留下一个小小的赞👍,你的支持是我分享的最大动力!

如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正

星辰大海

相关文章推荐
类和对象合集系列
类和对象(下)
类和对象(中)
类和对象(上)

===============

C++入门必备
C++入门基础

感谢支持

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
扎眼的阳光的头像扎眼的阳光普通用户
上一篇 2023年3月12日
下一篇 2023年3月12日

相关推荐