对于许多出初学C++的同学来说首先接触的第一个完整的类便是日期类,这个类能有效的帮助我们理解C++中有关类的初始化以及重载的相关知识,帮助我们轻松上手体验C++的魅力。
完成一个类,我们首先需要了解这个类需要完成什么任务,需要哪些函数来进行实现,以下是一个日期类的模板框架。
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);
};
在确认基本框架后,第一步是为我们的自定义类创建一个合适的构造函数,对于日期类而言,我们则需要在最开始实例化类的时候对日期进行一个判断,判断日期的合法性,例如年份大于1,月份在1-12之间,天数在对于的月份天数之内,具体的是实现如下:
//获取当前月份的日期
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;
}
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);
}
}
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);
}
思路上我们选择一个日期加上具体天数之后,采取逐一加天数的方式,每次加完之后进行判断,当前天数是否大于当月天数,同时当月数进一的时候判断当前月数是否大于十二,依次进位。
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)
{
*this = *this + day;
return *this;
}
思路上和日期加天数是一样的,但是不同的地方在于,当前天数小于1时,我们采取月份减一的方式,依次借位。
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;
}
我们在上面已经实现了日期加上天数以及两个日期之间是否相等的判断,在此可以直接复用,默认前一个日期大于后一个日期,将较小的日期天数逐次加一,当加到和较大天数相等时,此时加一的次数就是日期之间相差的天数。
//两个日期相减
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日正好是周一,因此我们计算某个日期减去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;
}
对于前置++和后置++运算符两者之间似乎操作数一样,但是不同之处在于后置++重载时有一个操作数,同时需要注意后置++运算符返回值应是修改前的值,对于 “–” 运算符与 “++“ 运算符基本一致,不在此列出。
Date& operator++()
{
return *this += 1;
}
Date operator++(int)
{
Date temp(*this);
*this += 1;
return temp;
}
相比于其他的运算符,流插入和流提取则有一丝不同,我们知道运算符重载时默认第一位是 this 指针,但是流插入和流提取第一个操作数明显是流插入运算符或流提取运算符,因此必须将其设为全局函数。
此时又会遇到另一个问题,全局函数如何访问类的私有成员,此时就需提前将其声明为友元函数在进行重载。
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;
}