基于我们学过的类的默认成员函数,接下来这一篇,我将解释如何实现日期类 Date :
// 函数默认在.cpp文件定义,所以需要指明类域。
我就默认是头文件分离的方式了。接下来我将展示日期类的函数声明代码:
#include<iostream>
#include<assert.h>
using namespace std;
class Date
{
public:
void Print();
// 获取某年某月的天数
int GetMonthDay(int year, int month);
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1);
// 拷贝构造函数
// d2(d1)
Date(const Date& d);
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& operator=(const Date& d);
// 析构函数
~Date();
// 日期+=天数
Date& operator+=(int day);
// 日期+天数
Date operator+(int day);
// 日期-天数
Date operator-(int day);
// 日期-=天数
Date& operator-=(int day);
// 前置++
Date& operator++();
// 后置++
Date operator++(int);
// 后置--
Date operator--(int);
// 前置--
Date& operator--();
// >运算符重载
bool operator>(const Date& d);
// ==运算符重载
bool operator==(const Date& d);
// >=运算符重载
bool operator >= (const Date& d);
// <运算符重载
bool operator < (const Date& d);
// <=运算符重载
bool operator <= (const Date& d);
// !=运算符重载
bool operator != (const Date& d);
// 日期-日期 返回天数
int operator-(const Date& d);
private:
int _year;
int _month;
int _day;
};如图,函数有很多,其中,不变的就3个内置类型:_year,_month,_day ,我们将围绕这三个展开函数:
此函数的目的就是打印日期,那么只需要调用cout就行:
void Date::Print ()
{
cout << _year << "-" << _month << "-" << _day << endl;
}所以也是很简单就拿下了。中间的分隔符我们用“-”表达,也可以换成其他的。
字面意思,这是获取某月天数的问题。既然是获取天数,那必然涉及到判断闰年的问题,并且返回
值是int,说明要返回这个月的天数。而这个很简单,C语言就接触过这种题目,我们只需要把12个
月份的日期写出来,找出2月份判断一下是否闰年,然后单独改变2月天数,然后返回就行。在这里
我直接展示代码:
int GetMonthDay(int year, int month)
{
assert(month > 0 && month < 13);
//static,不用每次都重新在栈帧中创建,方便点
static int monthDayArray[13] = { -1, 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 29;
}
else
{
return monthDayArray[month];
}
}这里用了很巧妙的方法,创建了一个数组,长度为13的数组,把索引0用-1(无意义的数字)代
替,剩下的索引1到12下标刚好与月数对应。然后还在数组前加了static,将数组变为静态,这样
就不用每次都重新在栈帧中创建删除,更方便,代码的可读性和效率都更高了。同时,用assert断
言判断了月份的合法性,使代码更完善。
只需要注意若定义分离在.cpp文件,不能重复声明缺省值 ,也就是说,在.h文件中声明并指定了缺
省值后,在.cpp文件定义就不需要写。然后,构造函数的目的是在对象实例化时给对象初始化,那
么就是很简单地对年月日赋值,最后别忘了构造函数的特点,函数名与类类型相同:
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}拷贝构造函数讲过,对成员变量全都是内置类型, 比如日期类Date 并且没有申请资源的,编译器
默认生成的拷贝构造就够用,所以不需要显式实现。它的逻辑就是对内置类型浅拷贝,也就是赋
值,所以和上面类似,对 对象中的年月日进行拷贝赋值就行:
Date::Date(const Date& d)
{
_year=d.year;
_month=d._month;
_day=d._day;
}与上面写的拷贝构造非常类似,经常有人搞混。需要注意,其返回值是 当前类类型的引用,也就
是Date&,然后就是对 对象的成员年月日 进行赋值:
Date& Date::operator=(const Date& d)
{
if (this != &d)//检查地址,相同则不需要赋值
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}如图。需要注意,因为赋值运算符重载针对的是两个已经存在的对象,所以他们地址可能相同,此
处最好加上一个检查自身赋值的 if 条件语句,进一步提高了代码的效率和健壮性。
Date类的成员变量都没有申请资源,所以不需要显式写。要写就是空着,比如:
Date::~Date()
{
}不过能不写就建议不写。因为写了没用的代码会导致带代码冗余。
日期+=天数,那最终返回的是Date类型,所以需要用引用返回,因为其出了函数作用域还存在。
参数只有一个int day是因为第一个参数为this指针。那么该怎么写这个函数?我的思路是:直接进
行+=操作:_day+=day; ,如果加完后天数大于当月天数,那就使月数++,并且减去当月的最大天
数。若月等于13月,则年数++,月数=1;考虑到加的天数可能很多,那就需要循环这个过程,所
以使用while循环:
while(_day>GetMonthDay(_year,_month))最后日期就求出来了。下面是代码:
Date& Date::operator+=(int day)
{
if (day < 0)
{
return *this -= -day;
}
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month == 13)
{
++_year;
_month = 1;
}
}
return *this;
}图中增加了如果输入负数天数的情况,此时就转化为*this -= -day,把加负天数转化为减正天数,这
也是复用了-=的函数,提高了代码效率。-= 的实现将在后面解释。
这个函数的实现逻辑与 日期+=天数 很像,区别就是日期+=天数会 改变被加数(日期)的值,而
日期+天数不改变原日期,只是得出一个新的值,被加数(日期)并未改变。
那么我们可以直接创建一个临时对象 tmp 对 *this 进行 浅拷贝,然后复用日期+=日期的函数,最
后返回tmp,而不返回*this 这样做到了不修改原日期,只得出结果。 代码如下:
Date Date::operator+(int day)
{
Date tmp(*this);
tmp += day;
return tmp;
}需要说明,因为返回的是临时对象tmp,其作用域只在函数内,所以不能使用引用返回
日期-=天数,会修改原对象,则最后返回*this指针。对于这个函数,我的思路是:先让他们相减,
如果天数大于0,说明月数没变,当月剩余天数比要减的大。如果小于0或者等于0,就需要重复+=
中月数和年数改变的操作,并且可能重复多次,所以用while循环:while(_day<=0) , 然后就能
得出答案,代码如下:
Date& 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;
}同样,如果要减去的天数小于0,则把 减负天数 转换成 加正天数 ,这样复用了+=函数,提高了效
率。+=函数的解释在上面,这两个函数相辅相成。
一样,可以复用日期-=天数,同时也需要注意,返回对象应是未经修改的原日期,那么直接同样创
建临时对象tmp对*this进行浅拷贝,最后返回tmp,代码:
Date Date::operator-(int day)
{
Date tmp(*this);
tmp -= day;
return tmp;
}因为返回的是临时对象tmp,其作用域只在函数内,所以不能使用引用返回
前置++,很简单,就是让天数加一天,可以直接复用+=,而且返回运算后的结果,也就是修改后
的日期:
Date& Date::operator++()
{
*this += 1;
return *this;
}这个和前置++一样,让天数加一天,但是不完全一样,因为是后置++,返回的应该是运算前的结
果,也就是原日期的值,并且原日期是被修改了的:
Date Date::operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}这里我们继续创建临时对象tmp,接收原日期的值,然后使原日期*this被改变,但是返回原日期
值。参数中加了一个无意义的int,只是为了区分前置++
和前后置++逻辑一样
Date& Date::operator--()
{
*this -= 1;
return *this;
}和前后置++逻辑一样
Date Date::operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}因为返回的是临时对象tmp,其作用域只在函数内,所以不能使用引用返回
运算符重载函数,重载大于号,那只需要在其中依次比较年月日大小即可,若年相同,比月,月相
同比日,有一个大就返回true,到最后了如果都一样就返回false,代码如下:
bool Date::operator>(const Date& d)
{
if (_year > d._year)
{
return true;
}
else if (_year == d._year)
{
if (_month > d._month)
{
return true;
}
else if (_month == d._month)
{
if (_day > d._day)
{
return true;
}
}
}
return false;
}运算符重载函数,重载了等于 == ,那么直接依次比较年月日,中间用并列符号 && ,直接返回
这串表达式即可,代码:
bool Date::operator==(const Date& d)
{
return _year == d._year && _month == d._month && _day == d._day;
}小于等于,那就是 < 的反面,那最简单的办法就是复用 > ,利用逻辑非(!)表达:
bool Date::operator <= (const Date& d)
{
return !(*this > d);
}bool Date::operator >= (const Date& d)
{
return (*this > d) || (*this == d);
}bool Date::operator < (const Date& d)
{
return (*this >= d);bool Date::operator != (const Date& d)
{
return !(*this == d);
}以上的逻辑类似,所以不作过多解释。
日期-日期 ,思路是 : 设置两个日期,一个max,一个min,用if比较(max>min)不是则反过
来。小的日期++,去追大的日期,期间day++,直到min大于max,循环条件while(min<max),最
后返回计数器day,然后flag代表+-代码如下:
int Date::operator-(const Date& d)
{
int flag = 1;
Date max = *this;
Date min = d;
if (max < min)
{
min = *this;
max = d;
flag=-1;
}
int day=0;
while (min < max)
{
++min;
++day;
}
return day*flag;
}