✨个人主页: 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#includeusing 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;elsereturn 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;elsereturn 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;elsereturn 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 += -100if (val < 0){//此时需要调用 -=*this -= (-val);return *this;}//因为是 += 不需要创建临时对象//首先把天数全部加至 _day 上_day += val;//获取当前月份天数int monthDay = getMonthDay();//判断进位,直至 _day <= monthDaywhile (_day > monthDay){//此时大于,先把多余的天数减掉_day -= monthDay;//此时进位一个月++_month;//判断月份是否大于 12if (_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++入门基础