日期类是一个比较简单的类,只包含内置成员变量,包含的成员函数见下面头文件:
#pragma once
#include <iostream>
#include <cassert>
using std::cout;
using std::cin;
using std::endl;
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1);//构造函数
Date(const Date& d); //拷贝构造函数
~Date(); //析构函数
void Show() const; //显示日期
int GetMonthDays(int year, int month) const; //取得某年某月的天数
//以下为运算符重载
Date& operator=(const Date& d);
bool operator==(const Date& d) const;
bool operator<(const Date& d) const;
bool operator<=(const Date& d) const;
bool operator>(const Date& d) const;
bool operator>=(const Date& d) const;
bool operator!=(const Date& d) const;
Date& operator+=(int days);
Date& operator+(int days) const;
Date& operator-=(int days);
Date& operator-(int days) const;
Date& operator++();//前置++
Date operator++(int);//后置++
Date& operator--();//前置--
Date operator--(int);//后置--
int operator-(const Date& d) const;//计算日期差
const char* WeekDay() const;//计算某个日期星期几
private:
int _year;
int _month;
int _day;
};
接下来我们看看相关函数如何实现。
构造函数
构造函数通常推荐使用全缺省或半缺省参数,灵活性比较好,当传了具体参数时会使用传过来的参数,没有传参时就使用缺省值。因为没有const成员变量,初始化操作可以使用初始化列表,也可以在函数体内赋值,要注意的是需要判断下传过来的值是否合法。
Date::Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{
if ((year <= 0) //判断传过来的日期是否合法
|| (_month < 1)
|| (_month > 12)
|| (_day < 1)
|| (_day > GetMonthDays(_year, _month)))
{
cout << "日期错误";
Show();
}
}
这里使用了一个封装的函数GetMonthDays,作用是取得某年某月的天数,使用一个静态数组是比较简便的实现方法:
int Date::GetMonthDays(int year, int month) const
{
const static int monthDays[] =
{ 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if ((month == 2) && (year % 4 == 0 && year % 100 != 0 || year % 400 == 0))
{
return monthDays[month] + 1; //判断是否是闰年的2月
}
return monthDays[month];
}
拷贝构造,析构
由于没有自定义成员变量,析构函数,拷贝构造函数和打印日期的函数比较简单:
Date::~Date()
{
}
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void Date::Show() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
赋值运算符的重载
赋值运算符用于将一个已经定义好了的对象赋值给另一个也定义好了的相同类型的对象。考虑到可能发生自己给自己赋值,所以里面对这种情况稍微优化了一下,直接返回自身即可:
// d1 = d2被转化成调用函数 d1.operator=(d2)
Date& Date::operator=(const Date& d)
{
if (this == &d) //如果自己给自己赋值,则直接返回自己
{
return *this;
}
this->_year = d._year;
this->_month = d._month;
this->_day = d._day;
return *this;
}
比较运算符
比较相等运算符==的重载
两个日期相等,则相当于对应的年月日都相等:
// d1 == d2 被转化为调用函数 d1.operator==(d2)
bool Date::operator==(const Date& d) const
{
return this->_year == d._year
&& this->_month == d._month
&& this->_day == d._day;
}
代码很简单,函数名那一行最后的const却很奇怪。我们知道,成员函数的参数列表中,其实第一个参数是隐含的this指针,类型是Date* const,这个const修饰this指针在函数体里面不能再指向其他值,比如如果在函数体里面写this = nullptr; 就会报错,但是*this是不受这个const约束的,也就是说成员变量的值可以通过*this来改变。当希望在函数体内对象的成员变量不能被修改时就可以在前面加上const,即const Date* const this,但因为this是隐含的,无法显式加const,C++规定可以在函数参数列表后面加上const来修饰*this。
小于运算符的重载
// d1 < d2 会调用d1.operator<(d2)
bool Date::operator<(const Date& d) const
{
//下面是小于比较的实现逻辑
if (this->_year < d._year)
{
return true;
}
else if (this->_year == d._year
&& this->_month < d._month)
{
return true;
}
else if (this->_year == d._year
&& this->_month == d._month
&& this->_day < d._day)
{
return true;
}
return false;
}
只有三种情况d1是小于d2的:
1. d1的年小于d2
2. 年相等的时候d1的月小于d2的月
3. 年相等月也相等的时候d1的天小于d2的天
其余情况下d1都不小于d2.
在实现了==和<后,其余四种比较运算符就可以复用==和<了:
bool Date::operator<=(const Date& d) const
{
return *this < d || *this == d;
}
bool Date::operator>(const Date& d) const
{
return !(*this <= d);
}
bool Date::operator>=(const Date& d) const
{
return !(*this < d);
}
bool Date::operator!=(const Date& d) const
{
return !(*this == d);
}
算数运算符
+=运算符的重载
//实现一个日期对象加上一个天数,返回相加后的对应日期
Date& Date::operator+=(int days)
{
assert(days >= 0);
_day += days;
while (_day > GetMonthDays(_year, _month))
{
_day -= GetMonthDays(_year, _month);
_month++;
if (_month > 12)
{
_month -= 12;
_year++;
}
}
return *this;
}
实现的逻辑是先把给定的日期天和要加的天数取和,循环判断天的合法性,不合法就把月++甚至年++,然后减掉相应月的天数,再回头判断,直到天数合法为止。
注意由于是+=,所以成员变量需要被改变,返回被改变的日期对象*this。
+运算符
+运算符和+=类似,唯一的区别在于+运算符并不改变自身成员变量,只返回+后的结果:
Date& Date::operator+(int days) const
{
return Date(*this) += days;//使用匿名对象
}
这里使用了匿名对象Date()并用*this拷贝构造它,然后返回它加上days之后的日期对象,原有的*this并未被改变。
-=和-运算符
使用类似的逻辑和代码
Date& Date::operator-=(int days)
{
assert(days >= 0);
_day -= days;
while (_day < 0)
{
if (_month == 1)
{
_year--;
_month = 12;
}
_month--;
_day += GetMonthDays(_year, _month);
}
return *this;
}
Date& Date::operator-(int days) const
{
return Date(*this) -= days;
}
前置和后置的++,--运算符
前置的++和--运算符使用正常的写法,由于是单目运算,所以参数列表为空,只有隐含的this指针参数;后置的++和--为了与之区分,C++规定在参数列表内多传一个int参数,这个int类型参数由编译器自动传值,不需要显式传。
Date& Date::operator++()
{
return *this += 1;
}
Date Date::operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}
Date& Date::operator--()
{
return *this -= 1;
}
Date Date::operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
-运算符的另一个重载——计算两个日期相差的天数
int Date::operator-(const Date& d) const
{
int flag = 1;
Date max(*this);
Date min(d);
if (max < min)
{
max = d;
min = *this;
flag = -1;
}
int count = 0;
while (min != max)
{
++min;
++count;
}
return count * flag;
}
算法有点暴力。
计算1900年1月1日之后某天是星期几
const char* Date::WeekDay() const
{
const char* weekArr[] =
{ "星期一","星期二", "星期三", "星期四", "星期五", "星期六", "星期日" };
Date bench(1900, 1, 1);
int gap = *this - bench;
return weekArr[gap % 7];
}