前言
前面我们说了类的六个默认成员函数:大家可以自行查看前面的内容:构造和析构函数、类和对象的默认成员函数下。
在讲默认成员函数下时我们有提过运算符重载函数,下面我们具体拓展一下各个符号的重载,并用其实现一个日期类。当然,它们使用的规则,可以应用到其他所有类中实现。
注意:运算符重载和函数重载没有直接的关系。
函数重载是让函数名相同,参数不同的函数存在。
运算符重载是让自定义类型可以使用运算符,并且控制运算符的行为,增强可读性。
但是多个同一运算符重载可构成函数重载
。
日期类的实现
要点
我们想要完成日期类时我们首先要知道它要干什么,它想要实现什么东西,下面我们来一一列举一下:
- 日期的大小比较
- 计算两个日期间隔的天数
- 日期+天数
- 日期-天数
- 前置++和后置++
日期检查
当我们在输入日期时,可能会将日期输错,此时我们可以写一个日期检查的函数,用来检查判断我们输入的日期是否正确:
bool Date::CheakDay()
{
if (_year <= 0 || _month <= 0 || _month > 12 || _day<0 || _day>GetMonthDay(_year, _month))
return false;
else
return true;
}
日期的大小比较
我们将需要的类和所涉及的成员函数及变量一一列出:
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{}
bool operator<(const Date& d1);
bool operator<=(const Date& d1);
bool operator>(const Date& d1);
bool operator>=(const Date& d1);
bool operator==(const Date& d1);
bool operator!=(const Date& d1);
private:
int _year;
int _month;
int _day;
};
下面,我们将这六个比较大小的函数一一实现。
首先我们来实现小于的运算符重载,我们根据年月日依次比较得出大小:
bool Date::operator<(const Date& d)//加const防止d被改变
{
if (_year < d._year)
return true;//年小就小
else if (_year == d._year)//年相同就比月
{
if (_month < d._month)
return true;
else if (_month == d._month)//月再相同就比日
return (_day < d._day);
}
return false;
}
我们来测试一下:
我们发现,如果我们给d2加了const,我们只能进行d1<d2的比较,而不能反过来写,这是涉及到const权限的问题,d2是const Date类型,而成员函数中隐含的this指针是Date const 类型,所以我们给成员函数加上const关键字进行修饰,使得this指针的类型变为const Date* const类型,使其能进行匹配,为了方便,我们将不用改变日期的函数都用const进行修饰。
这样可成功实现。
下面我们再来实现最简单的判断日期是否相等:
bool Date::operator==(const Date& d) const
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
运行测试比较简单,后期大家自行去实现。
下面继续来实现剩余四种判断,我们已经写了小于和等于的运行规则,剩下四个我们可以直接覆用小于和等于的规则即可,就不用单独再去实现了,如下所示:
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);
}
测试大家自行实现即可。
获取当月的天数
当我们要对日期进行加减时,要获取当月的天数是必不可少的,那我们就可以写一个函数,将其放到类中,使其变为内联函数,这样我们在使用时就能减少一些调用函数的损耗。
获取当月天数的函数代码如下:
int GetMonthDay(int year, int month)
{
assert(month > 0 && month < 13);
static int MonthDayArr[] = { 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 MonthDayArr[month];
}
我们发现,一个简单的获取当前月份天数的函数,其实藏着一些细节,一个就是assert断言
,如果月份不在1~12这个范围内就会报错,防止输入错误。第二个就是创建数组的时候我们用了一个static关键字
,使这个函数的生命周期延长,将其放在了静态区,不用每调用一次函数就要创建一次数组,这样减少了内存消耗。第三个就是创建数组的第一个元素用0代替,可以让数组下标与月份一一对应
,直接将月份作为下标输出即可。
日期+=天数
我们想要日期+=天数时,我们都会将其直接加到日(_day)上面,然后判断_day的天数是否超过了本月的天数,当超过时,我们就需要用_day减去当前月的天数,然后让月++,注意要判断是否会延伸到下一年,相信大家也都会,我就不再赘述。
Date& Date::operator+=(int n)
{
_day += n;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month == 13)
{
_month = 1;
++_year;
}
}
return *this;
}
我们使用了引用返回,减少了消耗,同时使用了前置++,同样也为了减少消耗,这点后面也会讲到。
注意:我们还要区分日期+=天数和日期+天数的区别,一个是会实际改变d的值,而日期+天数中d的值实际上是没有发生变化的,这点需要清楚。懂了这些区别,我们也就可以继续使用覆用来实现日期+天数:
Date Date::operator+(int n)
{
Date tmp = *this;
tmp += n;
return tmp;
}
这里我们使用值返回,是因为tmp出了函数作用域会被销毁,则不能用引用返回,具体可看上节所说的。我们用了tmp拷贝了*this,用tmp进行+=,这样返回加之后的值,但是 *this没有改变,达到了我们想要的目的。
日期-=天数
对于日期-=天数和日期+=天数的思路是一样的,都是按照常规去算即可,代码如下:
Date& Date::operator-=(int n)
{
_day -= n;
while (_day <= 0)
{
_month--;
if (_month == 0)
{
_month = 12;
_year--;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
同样我们用了引用返回,减少拷贝的消耗。
日期-天数也是同样的做法:
Date Date::operator-(int n)
{
Date tmp = *this;
tmp -= n;
return tmp;
}
前置++和后置++
我们要清楚的是前置++和后置++的区别是什么:
- 前置++:先加后使用,返回+1之后的结果。
- 后置++:先使用后加,返回+1之前的结果。
我们发现,如果直接写运算符重载函数,它们两都可以被写成:
Date operator++();
因此为了区分这两着,我们在后置++的括号里面加了一个int,即:
Date operator++(int);
虽然说括号里面有个int类型的形参,但是在调用函数时这个参数是不用传递的,编译器会自动传递,只是为了和前置++做出区分。
明白了这个代码写出来也就很简单了,代码如下:
Date& Date::operator++()//前置++
{
_day += 1;
return *this;
}
Date& Date::operator--()//前置--
{
_day -= 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;
}
我们可以发现,前置运算符都是用引用返回,而后置运算符都是用值返回,值返回会增加拷贝次数,所以为了减少消耗,提高效率,我们更推荐使用前置运算符。
日期减日期
在实现日期减日期时,我们首先要知道他们是如何相减得到的。
按照我们一般思路就是找到两个日期正常相减,但是我们可以直接用一个循环来实现,具体见代码:
int Date::operator-(const Date& d) const
{
int flag = 1;
int sum = 0;
Date max = *this;
Date min = d;
if (d > *this)
{
max = d;
min = *this;
flag = -1;
}
while (max != min)
{
++sum;
--max;
}
return flag * sum;
}
这也是一个很巧妙的代码,用了max和min,这样可以保证一直是max–。又多用了一个flag,如果日期1-日期2中,日期1比较小,可以使得得出的结果为负数,一目了然。
完整代码
有关日期类的实现已经全部完成了,下面是完整代码:
Date.h
#include<assert.h>
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{}
int GetMonthDay(int year, int month)
{
assert(month > 0 && month < 13);
static int MonthDayArr[] = { 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 MonthDayArr[month];
}
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
void Insert()
{
cin >> _year;
cin >> _month;
cin >> _day;
if (!CheakDay())
cout << "输入错误,请重新输入" << endl;
}
bool CheakDay();
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 n);
Date operator+(int n);
Date& operator-=(int n);
Date operator-(int n);
Date& operator++();
Date& operator--();
Date operator++(int);
Date operator--(int);
int operator-(const Date& d)const;
private:
int _year;
int _month;
int _day;
};
Date.cpp
#include"Date.h"
bool Date::CheakDay()
{
if (_year <= 0 || _month <= 0 || _month > 12 || _day<0 || _day>GetMonthDay(_year, _month))
return false;
else
return true;
}
bool Date::operator<(const Date& d) const
{
if (_year < d._year)
return true;
else if (_year == d._year)
{
if (_month < d._month)
return true;
else if (_month == d._month)
return (_day < d._day);
}
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 _year == d._year
&& _month == d._month
&& _day == d._day;
}
bool Date::operator!=(const Date& d) const
{
return !(*this == d);
}
Date& Date::operator+=(int n)
{
_day += n;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month == 13)
{
_month = 1;
++_year;
}
}
return *this;
}
Date Date::operator+(int n)
{
Date tmp = *this;
tmp += n;
return tmp;
}
Date& Date::operator-=(int n)
{
_day -= n;
while (_day <= 0)
{
_month--;
if (_month == 0)
{
_month = 12;
_year--;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
Date Date::operator-(int n)
{
Date tmp = *this;
tmp -= n;
return tmp;
}
Date& Date::operator++()//前置++
{
_day += 1;
return *this;
}
Date& Date::operator--()//前置--
{
_day -= 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;
}
int Date::operator-(const Date& d) const
{
int flag = 1;
int sum = 0;
Date max = *this;
Date min = d;
if (d > *this)
{
max = d;
min = *this;
flag = -1;
}
while (max != min)
{
++sum;
--max;
}
return flag * sum;
}
test.cpp
这属于调试代码,大家可以自行输入调试。
#include"Date.h"
int main()
{
Date d1(2024, 7, 24);
Date d2(2024, 7, 22);
cout << (d1 < d2) << endl;
cout << (d2 == d1) << endl;
cout << (d2 != d1) << endl;
cout << (d2 > d1) << endl;
cout << (d2 <= d1) << endl;
cout << (d2 >= d1) << endl;
d2 += 70;
d2.Print();
Date d3(2030, 7, 1);
cout << (d3 - d1) << endl;
return 0;
}
今天的内容到此结束啦,感谢大家观看,如果大家喜欢,希望大家一键三连支持一下,如有表述不正确,也欢迎大家批评指正。