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;
}
实现日期小于的比较
,我们要先思考日期的比较需要分别比较年月日
,从年开始依次比较,一旦成立则说明小于成立;反之应该是大于,小于不成立
🔥值得注意的是:
- 传参时要特别注意,
运算符有几个操作数,就传几个参数
,this指针也算在参数中
,
所以这里默认<左边的对象是用this指针
传过去了,右边的对象引用传参
(加const避免被传的数值被修改,引用传参提高效率) - 这里的运算符重载函数是
成员函数
调试状态下,转到反汇编比较可以观察到,两种写法的底层代码是一样的,说明了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;
}
后置++需要提前保存值
,因为是先使用再++
🔥值得注意的是:
前置++
和后置++
虽然是运算符重载
,但是形式上也构成函数重载
后置++
增加这个int参数不是为了接受具体的值
,仅仅是占位
,跟前置++
构成重载- 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的使用
能够很方便的进行输入输出的操作,那么日期依托于运算符重载
也能达到此类效果,cout
和cin
分别属于iostram库
里的ostream
和istream
2.5.1 <<运算符重载
根据前面的经验,我们依旧把<<运算符重载放在成员函数里
void Date::operator<<(ostream& out)
{
out << d._year << "年" << d._month << "月" << d._day;
}
可是当我们输出的时候就报错了,这是因为正常情况下cout<<d1
会转化成d1.operator<<cout
,Date对象默认占用第一个参数,也就是做了左操作数
,所以正确的写法应该是d1<<cout
才能正常输出,但是这样太奇怪了,完全不符合我们的使用习惯
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day;
return out;
}
既然不能写成成员函数,那就在类外写成非成员函数
,就可以带入两个参数,cout<<d1
就会转化成operator<<(cout,d1)
,符合书写情况
🔥值得注意的是:
- cout
从左往右
连续输出,所以要返回ostream类型才能连续输出
- 类外访问私有变量使用
友元函数
就能解决 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;
}