类和对象实操之【日期类】
✨个人主页: Yohifo
🎉所属专栏: C++修行之路
🎊每篇一句: 图片来源
- The pessimist complains about the wind; the optimist expects it to change; the realist adjusts the sails.
- 悲观主义者抱怨风;乐观主义者期望它改变;现实主义者调整风帆。
@[toc]
🗓️前言
在学完类和对象
相关知识后,需要一个程序来供我们练习、巩固知识点,日期类
就是我们练习的首选程序,日期类
实现简单且功能丰富,相信在完整地将日期类
实现后,能对类和对象
有更好的掌握及更深的理解
🗓️正文
为了更符合工程标准,这里采用三个文件的方式实现程序
用于声明类和方法的 .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;
};
}
📅合法性检验
首先编写第一个函数:合法性检验
检验标准
- 年不能为0
- 月在区间 [1, 12] 内,超过为非法
- 根据年月推算出天数,天数不能操作规定天数,也不能
<= 0
==注意:==
- 当前包括后续函数都是采取先在头文件
Date.h
的类中声明,再到Date.cpp
实现的路径 - 因历史原因导致的闰年变动这里不考虑,该程序实现的是理想情况下的闰年状态
- 程序计算范围覆盖至公元前,限度为
[INT_MIN, INT_MAX]
#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));
}
📅重载流插入、提取
cout
、cin
只能输出、输出内置类型,但如果我们对它进行改造一下,就能直接输出我们的自定义类型
==注意:==
cout
类型为ostream
,cin
类型为istream
- 要使得
cout
、cin
变为重载后的左操作数,此时的运算符重载就不能写在类内,因为在类中的函数默认this
为第一个参数,即左操作数 - 因此这两个函数比较特殊,需要写在外面,但同时又得访问类中的成员,此时就需要
友元函数
- 两个函数都有返回值,返回的就是
cout
、cin
本身,避免出现cout << d1 << d2
这种情况
此时可以利用合法性检验了
==实现 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; //对自定义类型的输出
📆日期+=天数
==下面涉及两个重要算法==
- 日期 += 天数
- 日期 -= 天数
这里把 日期 += 天数
介绍清楚了,日期 -= 天数
就很好写了,就是倒着走
- 有了
日期 += 天数
后,可以直接实现日期 + 天数
- 同理也可以实现
日期 - 天数
📅核心思想
==注:此时实现的是 日期+=天数==
进位思想:天数满了后进位到月份上,月份满后进位至年份上
==注意:==
- 每个月对应天数都需要计算,因为每个月都不同
- 月份为12月时,再+就变成了下一年的一月
- 假设为公元前,加至0年时,需要特殊处理为公元1年
+=
操作返回的是左操作数本身,应对(d1 += 10) = 20
这种情况
📅代码实现
//日期+=天数
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 仓库中
📆日期-日期
==日期+日期无意义,但日期-日期有,可以计算两日期差值==
日期相减有两种情况:
左操作数
小于右操作数
,此时返回大于0的值左操作数
大于右操作数
,此时返回小于0的值
具体实现时也很好处理,直接用一个 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)