目录
一、日期类的函数声明
二、 构造函数
三.关系判断操作符重载
3.1 大于(>)运算符重载
3.2 等于(=)运算符重载
3.3 其余运算符重载
四、日期类的加减操作
4.1 日期 +/+= 天数
4.2 日期 -/-= 天数
4.3 计算日期差值
4.4 推算当日星期
五、前置操作符与后置操作符
5.1 ++ 操作符
5.2 -- 操作符
六、流插入和流提取操作符重载
本篇博客主要是为了实现日期类,实现这个类我们便可很轻松的实现关于日期的计算、比较、推演等等,我们开始吧。
先来看看我们的日期类要实现哪些功能。
class Date
{
//检查日期合法
bool CheckDate();
//获取每月天数
int GetMonthDay(int year, int month);
//构造函数
Date(int year = 1900, int month = 1, int day = 1);
//关系判断操作符重载
bool operator>(const Date& d1);
bool operator== (const Date& d2);
bool operator>=(const Date& d1);
bool operator<(const Date& d1);
bool operator<=(const Date& d1);
bool operator!=(const Date& d1);
//日期类的加减操作
Date& operator +=(int day);
Date operator +(int day);
Date& operator-=(int day);
Date operator-(int day);
//日期减去日期
int operator- (const Date& d);
//计算当前天数为星期几
void WeekDay();
//前置、后置操作符
Date& operator++();
Date operator++(int);
Date& operator--();
Date operator--(int);
//流插入和流提取操作符重载
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
};
总共分为4大部分:
- 日期类的默认成员函数——(构造函数)
- 日期类的关系判断操作符重载
- 日期类的加减操作
- 流插入、流提取操作符重载
实现了以上4个部分,我们的日期类就基本实现了,有关于日期的一系列操作就都可以实现了。
我们都知道,C++编译器生成的默认构造函数对内置类型不做处理,所以我们需要编写构造函数来将新创建的对象初始化,并判断用户输入的是否为非法日期(例如2022/2/29、2022/3/40)。
Date(int year=1900,int month=1,int day=1)
{
_year = year;
_month = month;
_day = day;
//检查生成的日期是否非法
if (!CheckDate())
{
cout << "日期非法:" ;
Print();
//退出程序,正常退出exit(0),非法退出exit(非0);
exit(-1);
}
}
这必然要书写一个函数(CheckDate)来判断日期是否合法,和一个返回当前月份天数的函数。
//获取当前月份的日期
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;
}
bool CheckDate()
{
if (_year < 1 || _month>13 || _month < 1 || _day<1 || _day>GetMonthDay(_year, _month))
return false;
return true;
}
日期类这种类,我们不需要手动书写析构函数、拷贝构造、赋值运算符重载……这些默认构造函数。
判断一个日期是否大于另一个日期,我们可以先列举出所有大于的情况,如果不满足则直接返回 false。代码如下,这里就不多赘述了。
bool operator>(const Date& d1)
{
//举例出所有大于的情况
if (_year > d1._year)
{
return true;
}
else
{
if (_year==d1._year &&_month > d1._month)
{
return true;
}
else if (_year == d1._year && _month >= d1._month && _day > d1._day)
{
return true;
}
else
{
return false;
}
}
}
当然,我们还可以使用简便上面的那种写法,比如这样:
bool operator>(const Date& d1)
{
if ((_year > d1._year)
|| (_year >= d1._year && _month > d1._month)
|| (_year >= d1._year && _month >= d1._month && _day > d1._day))
{
return true;
}
return false;
}
bool operator== (const Date& d2)
{
return _year == d2._year
&& _month == d2._month
&& _day == d2._day;
}
我们实现了 > 、 = 两个运算符重载,此时还有>=、< 、 <= 、!= 这四个运算符重载没有实现,这里,我们可以复用> 、 = 两个运算符重载来实现剩下的四个运算符重载。
bool operator>=(const Date& d1)
{
return (*this > d1) || (*this == d1);
}
bool operator<(const Date& d1)
{
return !(*this >= d1);
}
bool operator<=(const Date& d1)
{
return !(*this > d1);
}
bool operator!=(const Date& d1)
{
return !(*this == d1);
}
关于日期+天数,我们可以拷贝构造一个临时变量,然后对该临时变量进行操作,然后将临时变量的值进行返回。
- 拷贝构造一个临时变量
- 如果传入的day<0,则调用 - 操作符重载(稍后实现)
- 将 day 全部加到 _day 上
- 当 _day 的大于该月的天数时减去改月的天数,将_month++。直到_day小于等于该月的天数。
Date operator +(int day)
{
//如果day 是负数 调用-操作符重载
if (day < 0)
{
return *this - (-day);
}
Date d1(*this);
//直接加到_day上,直到_day合法。
d1._day += day;
while (d1._day > GetMonthDay(d1._year, d1._month))
{
d1._day -= GetMonthDay(d1._year, d1._month);
++d1._month;
if (d1._month == 13)
{
d1._month = 1;
d1._year++;
}
}
return d1;
}
接下来就是实现 += 天数了,+= 天数不需要创建临时变量就可以进行操作,与+天数的思路一摸一样,所以我们还可以复用+操作符重载。
Date& operator +=(int day)
{
//如果day 是负数
if (day < 0)
{
return *this -= -day;
}
//直接加到_day上,直到_day合法。
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month == 13)
{
_month = 1;
_year++;
}
}
return *this;
}
我们可以复用 + 操作符重载。
Date& operator +=(int day)
{
//但是这样会多次调用拷贝构造,效率比上面的写法要低
*this = *this + day;
return *this;
}
加法实现了,接下来就是实现 -= 操作符重载。
Date& operator-=(int day)
{
//如果减去负天数 ,则调用 +=
if (day < 0)
{
return *this += -day;
}
//直接减去
_day -= day;
//借位减去天数,直到天数合法
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
//加上天数
_day += GetMonthDay(_year, _month);
}
return *this;
}
- 操作符重载我们就直接调用即可。
Date operator-(int day)
{
Date temp(*this);
temp -= day;
return temp;
}
思路:
- 假设 *this > d ,用 *this 拷贝构造一个 max,用 d 拷贝构造一个 min。
- 设立一个 flag = 1,表示 *this >d 。然后调用 > 操作符重载,判断哪个对象大,如果假设错误则将 max 和 min 交换,并将 flat 置为 -1 。
- 设立 count 统计天数,让 min 对象不断++ ,直到min == max,则跳出循环。
- 此时返回 count*flag。
//两个日期相减
int operator- (const Date & d)
{
//先假设 *this > d
int flag = 1;
Date max = *this;
Date min = d;
if (*this < d)
{
max = d;
min = *this;
flag = -1;
}
int count = 0;
while (min != max)
{
++min;
++count;
}
return count * flag;
}
我们计算1900年1月1日之后的天数为星期几,因为推算星期几这个问题在历史上有这样两个原因:
1.教皇删除了历史上的10天
2.闰年的确立
所以我们从1900年1月1日开始计算,就避开了这两个问题,并且好在1900年1月1日正好就是星期一。
//判断当前日期是周几
void WeekDay( )
{
Date statr(1900, 1, 1);
//求相差的天数
int n = *this - statr;
//5相当于是周6
int weekday = 0;
weekday += n;
cout << "周" << weekday % 7 + 1 << endl;
}
因为我们涉及到了前置++和后置++,但是此操作符只有一个操作数——this,所以无法区分哪个是前置++,哪个是后置++。因此,C++规定了,operator++()为前置++,而operator++(int)则是后置++,后置++中的 int 只是用于区分两个操作符构成函数重载,其自身并没有什么特殊含义。
//写法一:
Date& operator++()
{
*this += 1;
return *this;
}
//写法二:
Date& operator++()
{
return *this += 1;
}
后置++,则要拷贝构造一个临时变量,再将*this+= 1,然后返回临时变量的值。
Date operator++(int)
{
Date temp(*this);
*this += 1;
return temp;
}
//前置--
Date& operator--()
{
return *this -= 1;
}
//后置--
Date operator--(int)
{
Date temp(*this);
*this -= 1;
return temp;
}
实现流插入和流提取操作符重载可以很方便我们再控制台输入和打印。
大家看一下下面这样的实现可以吗。
ostream& operator<<(ostream& out)
{
out << _year << '/' << _month << '/' << _day;
return out;
}
如果像上图一样调用,因为第一个参数为this指针,所以我们调用只能这样调用
发现d1在左,而out在右,这样很明显不符合正常的输出形式。
所以我们只能重载为全局的函数,但要注意以下几点:
- 使用友元函数声明(即可访问私有成员)。
- cin 对应 istream类型,cout 对应 ostream 类型,并使用引用传参。
- 流提取时,对象要使用 const 修饰,而流插入时,不能使用 const 修饰。
- 使用引用做返回值,这样就可以连续插入、提取。
在 Date 类中进行友元函数声明
ostream& operator<<(ostream& out,const Date& d)
{
out << d._year << '/' << d._month << '/' << d._day;
return out;
}
istream& operator>>(istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
//检查输入格式是否正确
assert(d.CheckDate());
return in;
}
使用举例: