一、引言
对于像int、double等这种C++的内置类型来说,加减运算是比较简单的。但是如果想对两个日期类对象进行相减运算,得到两个日期相差多少天又应该怎么操作呢。为此,C++引用了一种叫运算符重载的方法来解决上述问题。
二、简介
- 运算符重载是具有特定名字的函数,他的名字是由operator和后面要定义的运算符共同构成。和其他函数一样,他也具有返回类型和参数列表以及函数体。
- 重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。一元运算符有一个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数
- 如果一个重载运算符函数是成员函数,则他的第一个运算对象默认传给隐式的this指针
三、日期类实现运算符重载
头文件用于类成员及其成员函数的声明,代码如下
#pragma once
#include <iostream>
using namespace std;
bool isLeap(int year);
//距离元时间有多少天
int ToOriginDay(int year, int month, int day);
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 = 1900, int month = 1, int day = 1);
// 拷贝构造函数
// d2(d1)
Date(const Date& d);
//检查日期是否合法
bool CheckValue(const Date& d);
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& operator=(const Date& d);
// 析构函数
~Date();
//打印日期
void Print();
// 日期+=天数
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;
};
全缺省构造函数
缺省构造函数只需在声明或者定义一处标明缺省值即可,否则会出现重定义的情况
// 全缺省的构造函数
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
拷贝构造函数:用于拷贝一个类的成员到另一个类中
// 拷贝构造函数
// d2(d1)
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
赋值运算符重载:用于把一个类的成员赋值给另一个类的成员,因为返回值为一个该类的类引用,因此 返回该类的this指针指向的类对象,也就是*this
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
析构函数:将年月日置零
// 析构函数
Date::~Date()
{
_year = _month = _day = 0;
}
打印日期:
//打印日期
void Date::Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
检查日期是否合法:
//检查日期是否合法
bool Date::CheckValue(const Date& d)
{
if ((d._month > 0 && d._month < 13) && (d._day > 0 && d._day <= GetMonthDay(d._year, d._month)))
return true;
return false;
}
为了后续运算符重载的运算,这里提供三个函数,分别用于判断是否为闰年;传入日期距离0年0月0日(虽然没有0年0月0日,但为了方便计算可以设这个日期)有多少天,这里默认0年0月0日为元时间(当然,你也可以自定义一个元日期例如1900年1月1日,到时候修改i的初始值即可),待会减运算时会用到;获取某年某月有多少天。
bool isLeap(int year)
{
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
//距离元时间有多少天
int ToOriginDay(int year, int month, int day) {
int subyear = year - 0;
int i = 0;
int days = 0;
for (i = 0; i < subyear; i++) {
if (isLeap(i))
days += 366;
else
days += 365;
}
int a[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (isLeap(year))
a[2] = 29;
for (i = 0; i < month; i++) {
days += a[i];
}
days += day;
return days;
}
// 获取某年某月的天数
int Date::GetMonthDay(int year, int month)
{
static int a[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && isLeap(year))
return 29;
return a[month];
}
+=和+运算符重载: +=和+运算符重载用于给定一个日期类和一个天数day,求出该日期之后day天是哪一天,但是+=要求原日期改变而+要求原日期不变
对于+=来说,如果是像计算2024年8月17日之后10天这种不涉及跨月的就十分简单了,但是如果要计算100天后甚至1000天后是哪一天还真不好口算。其实,计算这个问题只需解决进位问题即可。我们可以_day先+=day,然后通过循环解决进位。如果现在的_day大于该月最大的天数,证明需要进位到下一个月,则进入循环。进入循环后,先让_day减去距离该月月底的天数,再让月份进一,如果月份为13月则让他重新从1月开始,同年份也要加一年。如此循环,最后返回*this即可
对于+来说,我们可以复用+=运算减轻工作量,定义一个tmp类并把*this给给tmp,让tmp进行+=运算后返回tmp即可
// 日期+=天数
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)
{
Date tmp = *this;
tmp += day;
return 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)
{
Date tmp = *this;
tmp -= day;
return tmp;
}
在弄明白+=、+、-=和-后,对于前置++、后置++、前置--、后置--便能触类旁通了,因为他们分别也就是+=一天、+一天、-=一天和-一天而已
// 前置++
Date& Date::operator++()
{
*this += 1;
return *this;
}
// 后置++
Date Date::operator++(int)
{
Date tmp = *this;
*this += 1;
return tmp;
}
// 前置--
Date& Date::operator--()
{
*this -= 1;
return *this;
}
// 后置--
Date Date::operator--(int)
{
Date tmp = *this;
*this -= 1;
return tmp;
}
对于两个日期相减,用于计算两个日期之间差了多少天,就要用到元日期了。先计算出第一个日期离元日期有多少天,再计算出第二个日期离元日期有多少天,然后分别让这两个天数相减就可以计算出两个日期间相差的天数了。
//距离元时间有多少天
int ToOriginDay(int year, int month, int day) {
//假设为2024/8/13
int subyear = year - 0;
int i = 0;
int days = 0;
for (i = 0; i < subyear; i++) {
if (isLeap(i))
days += 366;
else
days += 365;
}
int a[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (isLeap(year))
a[2] = 29;
for (i = 0; i < month; i++) {
days += a[i];
}
days += day;
return days;
}
// 日期-日期 返回天数
int Date::operator-(const Date& d)
{
int day1 = ToOriginDay(_year, _month, _day);
int day2 = ToOriginDay(d._year, d._month, d._day);
if(day1 > day2)
return day1 - day2;
else
return day2 - day1;
}
日期类可以计算,同样的日期类也可以比较。两个日期之间进行比较就可以知道哪个日期先哪个日期后。
对于>来说,也就是一个日期在另一个日期后面。那么只要年份比你大就大,或者年份相等月份比你大就大,又或者年份月份都相等日期比你大就大。对于==来说就是年月日都一样。对于>=,就可以调用>和=了,只要我大于或者等于你就满足条件。剩下的日期比较逻辑也都类似。
// >运算符重载
bool Date::operator>(const Date& d)
{
if (_year > d._year)
return true;
if (_year == d._year) {
if (_month > d._month)
return true;
if (_month == d._month && _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 || *this == d;
}
// <运算符重载
bool Date::operator<(const Date& d)
{
if (_year < d._year)
return true;
if (_year == d._year) {
if (_month < d._month)
return true;
if (_month == d._month && _day < d._day)
return true;
}
return false;
}
// <=运算符重载
bool Date::operator <= (const Date& d)
{
return *this < d || *this == d;
}
// !=运算符重载
bool Date::operator != (const Date& d)
{
return !(*this == d);
}
对于输入输出流重载,类外实现就要定义成友元函数。分别返回ostream&和istream&即可
//输出流重载
ostream& operator<< (ostream& out, const Date& d)
{
out << d._year << "/" << d._month << "/" << d._day << endl;
return out;
}
//检查日期是否合法
bool Date::CheckValue(const Date& d)
{
if ((d._month > 0 && d._month < 13) && (d._day > 0 && d._day <= GetMonthDay(d._year, d._month)))
return true;
return false;
}
//输入流重载
istream& operator>> (istream& in, Date& d)
{
while (1) {
cout << "请输入合法日期:";
in >> d._year >> d._month >> d._day;
if (!d.CheckValue(d)) {
cout << "非法日期" <<endl;
}
else {
break;
}
}
return in;
}