日期类的实现
在前面类和对象的学习中,由于知识多比较多和碎,需要一个能够将之前所学知识融会贯通的东西。下面就通过实现日期类来对类和对象已经所学的知识进行巩固。
日期类的基本功能(.h文件)
//Date.h//头文件内容
#include<iostream>
#include<assert.h>
using namespace std;
class Date
{
friend ostream& operator <<(ostream& out, const Date& d);
friend istream& operator >>(istream& in, Date& d);
public:
// 获取某年某月的天数
int GetMonthDay(int year, int month);
// 全缺省的构造函数
Date(int year = 2023, int month = 5, int day = 16);
// 日期+=天数
Date& operator+=(int day);
// 日期+天数
Date operator+(int day)const;
// 日期-天数
Date operator-(int day)const;
// 日期-=天数
Date& operator-=(int day);
// 前置++
Date& operator++();
// 后置++
Date operator++(int);
// 后置--
Date operator--(int);
// 前置--
Date& operator--();
// >运算符重载
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;
// 日期-日期 返回天数
int operator-(const Date& d)const;
void Print()const
{
std::cout << _year << " "
<< _month << " "
<< _day << std::endl;
}
private:
int _year;
int _month;
int _day;
};
ostream& operator <<(ostream& out, const Date& d);
istream& operator >>(istream& in, Date& d);
默认构造函数的实现
由于实现的是日期类的成员对象都是内置类型,所以构造函数、析构函数、拷贝构造、赋值符重载这些都可以使用编译器默认生成的。这里我就实现一个带缺省参数的构造函数即可。
带缺省参数构造的实现
其实日期类这种都是内置类型的类并不需要我们自己写构造函数,但是为了复习与回顾知识点还是写上。采取声明定义分离的方式来实现。
//Date.h中放声明
Date(int year = 2023, int month = 5, int day = 16);
// Date.c中实现
// 全缺省的构造函数
//Date:: 用于指明类域
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
需要注意的是当声明和定义分离时,我们只能将缺省参数放在声明中,定义内就不能出现缺省参数。不然会和编译器的默认构造函数起冲突。
比较运算符重载的实现
在日常生活中不免的会对另个日期进行比较,并且在后面的模块中还会需要日期类的比较功能。下面我们就来实现一下比较运算功能。
等于运算符重载的实现
对于两个日期类等于的判断条件无非就是年、月和日都相等。
//()后的const修饰的是this指针,不改变成员变量,可以用const修饰this指针
bool Date::operator==(const Date& d)const
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
小于运算符重载的实现
此次比较年月日,只要比第一个操作数小,就返回真,否则返回假。
bool Date::operator<(const Date& d)const
{
if(_year < d._year)
return true;
else if(_month < d._month)
return true;
else if(_day < d._day)
return true;
else
return false;
}
其他比较运算符重载的实现
其实,当完成了小于和等于运算符重载时,我们就可以复用这两个运算符重载的内容。来实现其他比较运算符的功能了。比如要实现大于运算符重载时,只需用在小于等于运算符重载的基础上进行逻辑取反即可。
//重载小于等于
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);
}
日期的加减功能的实现
首先,我们需要明确的一点是运算符重载必须要重载有意义的运算符。所以像日期+日期这类并没有实际意义的就没必要进行运算符重载。
每月天数获取接口
在实现日期加减运算符重载难免需要频繁获取月份所对应的天数,所以这里我直接把它封装成一个接口。接口的实现思路如下:开辟静态数组存储月份对应的日期,利用数组下标与月份天数的绝对映射来判断每月的天数。当月份为二月时,判断是否为闰年,若是闰年就返回29天。将该函数用static修饰成静态成员函数可以制定类域访问该成员函数,这个在下面会有使用场景。
static int Date::GetMonthDay(int year, int month)
{
//使用静态数组,可以避免频繁创建数组,提升效率。
static int DaysArr[13] = {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 29;
else
return DaysArr[month];
}
日期+=天数的实现
实现思路如下:首先,日期加的天数。然后,如果日期天数大于月份天数循环让天数减去该月天数。直到天数小于等于该月天数。
Date& Date::operator+=(int day)
{
_day += day;
while(_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if(_month == 13)
{
_month = 1;
_year++;
}
}
return *this;
}
日期+天数的实现
日期+天数,原来的日期不能变。这就是+和+=的区别。日期加天数的实现这里只需要复用一下+=即可。
Date Date::operator+(int day)const
{
Date tmp = *this;
tmp += day;
return tmp;
}
日期-=天数和日期类-天数的实现
日期-=天数实现思路如下:先让日期类天数-=天数,此时如果日期类的天数依旧大于0,则直接返回日期类。若日期类的天数小于等于0,则让月份先–,然后判断月份有效性,最后让日期类天数+=该月份天数直至日期类天数大于0。日期类-天数的实现只需要创建临时变量并拷贝构造为*this,然后让tmp-=天数,返回tmp即可。
// 日期-=天数
Date& Date::operator-=(int day)
{
_day -= day;
while(_day <= 0)
{
_month--;
if(_month == 0)
{
_month = 12;
_year--;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
// 日期-天数
Date Date::operator-(int day)const
{
Date tmp = *this;
tmp -= day;
return tmp;
}
优化+= 和 -=运算符重载
由于天数有可能为负数。所以,我们需要对+=和-=的函数重载进行优化。当天数为负数时,+=应该去调用-=,-=应该去调用+=。优化后代码如下
// 日期-=天数
Date& Date::operator-=(int day)
{
if(day < 0)
{
return *this += (-day);
}
_day -= day;
while(_day <= 0)
{
_month--;
if(_month == 0)
{
_month = 12;
_year--;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
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)
{
_month = 1;
_year++;
}
}
return *this;
}
前置++和后置++的实现
c++规定前置++运算符重载是无参数的,而后置++带有类型参数以示区别。前置++现将对象的自身值自增1后,返回对象。后置++先将对象的值保存到临时变量中,对象值自增1后,返回临时变量。
// 前置++
Date& Date::operator++()
{
*this += 1;
return *this;
}
// 后置++
Date Date::operator++(int)
{
Date tmp = *this;
*this += 1;
return tmp;
}
前置–和后置–的实现
// 后置--
Date Date::operator--(int)
{
Date tmp = *this;
*this -= 1;
return tmp;
}
// 前置--
Date& Date::operator--()
{
return *this -= 1;
}
日期-日期返回天数的实现
实现思路如下:默认左操作数为较大值,并且默认天数为负数。如果右操作数大于左操作数,则较大值为右操作符,且天数为正数。循环++较小的操作数,并记录循环次数。直到两个操作数相等。
// 日期-日期 返回天数
int Date::operator-(const Date& d)const
{
//默认为负数
int flag = -1;
Date max = *this;
Date min = d;
if(d > *this)
{
max = d;
min = *this;
//天数为正数
flag = 1;
}
int n = 0;
while(min != max)
{
min++;
n++;
}
return n*flag;
}
<< 和 >>运算符重载
首先,我们先了解一下cout和cin这两个类对象。
为什么cout << 内置类型变量,就可以自动识别内置类型变量呢?这是因为c++标准库对<<运算符进行了运算符重载。而多个内置类型的运算符重载又构成了函数重载。
#include<iostream>
using namespace std;
int main
{
int a = 10;
double d =20.11
cout << a;
cout << d;
}
而重载<<运算符,第一个参数必须是cout。而成员函数默认占用成员函数的第一个参数。所以这里这能进<<运算符重载到全局中。那么第二个问题来了,如何让全局函数突破类域的限制呢?这里就要用到友元关键字来修饰函数了。友元函数可以突破类域的限制。我们将友元函数的声明放在类的内部。这样友元函数就可以突破类域限制,访问类的私有成员变量。
friend ostream& operator <<(ostream& out, const Date& d);
friend istream& operator >>(istream& in, Date& d);
ostream& operator <<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
关于不存在日期的解决问题
这里为了避免天数月份小于1,月份大于12,天数大于月数的最大天数。所以我们应该在构造函数和>>运算符重载中,对日期进行相应的判断。
Date::Date(int year, int month, int day)
{
if((month > 12 || month < 1) ||
(day < 1||day > GetMonthDay(year,month))
)
{
cout<<"输入错误"<<endl;
assert(false);
}
else
{
_year = year;
_month = month;
_day = day;
}
}
istream& operator >>(istream& in, Date& d)
{
int year,month,day;
in >> year >> month >> day;
if((month > 12 || month < 1) ||
(day < 1||day > d.GetMonthDay(year,month))
)
{
cout<<"输入错误"<<endl;
assert(false);
}
else
{
d._year = year;
d._month = month;
d._day = day;
}
return in;
}