目录
一、前言
二、日期类的实现
检查日期的合法性
< 运算符重载
==运算符重载
<=运算符重载
>运算符重载
>=运算符重载
!=运算符重载
进一步优化
日期+天数
日期+=天数
日期-=天数
日期-天数
前置++&&后置++
前置--&&后置--
思路:
日期-日期
三、总代码
Date.h文件
Date.cpp文件
一、前言
在我们的日常生活中,我们可能需要计算几天后的日期或者计算日期差等,我们计算日期可以直接用日期计算器来求得,下面我们先看一下网络上面的日期计算器截图:
现在我们就用代码自己实现一个日期计算器。
二、日期类的实现
检查日期的合法性
实现日期类的首要前提肯定就是先要检查日期的合法性。
class Date { public: bool isLeapYear(int year) //判断是否为闰年 { return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); } int GetMonthDay(int year, int month) { assert(year >= 0 && month > 0 && month < 13); //加上static防止函数频繁调用开辟几十个字节大小的数组 static int monthDayArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; if (month == 2 && isLeapYear(year)) return 29; //闰月29天 else return monthDayArray[month]; } Date(int year = 1, int month = 1, int day = 1) { if (year >= 1 && month <= 12 && month >= 1 && day <= GetMonthDay(year, month)) { //确保日期合法 _year = year; _month = month; _day = day; } } private: int _year; int _month; int _day; };
< 运算符重载
- 思路:
运算符重载我们在上篇博文中已经讲过,这里就不再过多赘述,现在我们要写出<运算符重载,假如我们实例化出对象d1和d2,我们比较d1是否小于d2,只需要考虑如下三种情况:
- d1的年小于d2的年
- d1与d2的年相等,d1的月小于d2的月
- d1与d2的年和月都相等,d1的天小于d2的天
这三种情况任何一种情况符合,都属于d1小于d2。
- 代码如下:
// <运算符重载 bool Date::operator<(const Date& d) const //类外访问成员函数需要设定类域 { if (_year < d._year || _year == d._year && _month < d._month || _year == d._year && _month == d._month && _day < d._day) return true; else return false; }
==运算符重载
- 思路:
==运算符重载只需要判定d1和d2的年、月、日对应是否相等。
- 代码如下:
// ==运算符重载 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); }
进一步优化
- 我们上面所写的运算符重载都是建立在声明和定义分离的,这里我们可以对其进行进一步的优化。我们以前学过内敛函数,并且对于类来说直接定义在类里面的函数默认为内敛函数,所以我们将代码量小的函数直接定义在类里面,代码量大的函数进行声明和定义分离。
- Dath.h文件:
#pragma once #include <iostream> #include <assert.h> using std::cout; using std::cin; using std::endl; class Date { public: bool isLeapYear(int year) //判断是否为闰年 { return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); } int GetMonthDay(int year, int month);//获取某年某月天数 //构造函数 Date(int year = 1, int month = 1, int day = 1); //打印 void Print() const { cout << _year << "-" << _month << "-" << _day << endl; } //<运算符重载 bool operator<(const Date& d) const; //==运算符重载 bool operator==(const Date& d)const; //<=运算符重载 bool operator<=(const Date& d)const { return *this < d || *this == d; } //>运算符重载 bool operator>(const Date& d)const { return !(*this <= d); } //>=运算符重载 bool operator>=(const Date& d)const { return !(*this < d); } //!=运算符重载 bool operator!=(const Date& d)const { return !(*this == d); } private: int _year; int _month; int _day; };
- Dath.cpp文件:
#include "Date.h" //<运算符重载 bool Date::operator<(const Date& d)const { if (_year < d._year || _year == d._year && _month < d._month || _year == d._year && _month == d._month && _day < d._day) return true; else return false; } //==运算符重载 bool Date::operator==(const Date& d)const { return _year == d._year && _month == d._month && _day == d._day; } //获取某年某月天数 int Date::GetMonthDay(int year, int month) { assert(year >= 0 && month > 0 && month < 13); static int monthDayArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; if (month == 2 && isLeapYear(year)) { return 29; } else { return monthDayArray[month]; } } //构造函数 Date::Date(int year, int month, int day) { if (year >= 1 && month <= 12 && month >= 1 && day >= 1 && day <= GetMonthDay(year, month)) { _year = year; _month = month; _day = day; } else { cout << "日期非法" << endl; } }
日期+天数
- 思路:
日期+天数我们得到的还是一个日期,需要注意的就是进位的问题(天满了往月进,月满了往年进)。
- 加过天数后超过该月的最大天数,需要进位。
- 当月进位到13后,年进位+1,月重置为1。
- 法一:
Date Date::operator+(int day) const { Date ret(*this); //拷贝构造,拿d1去初始化ret ret._day += day; while (ret._day > GetMonthDay(ret._year, ret._month)) { ret._day -= GetMonthDay(ret._year, ret._month); ret._month++; if (ret._month == 13) { ret._year++; ret._month = 1; } } return ret; }
出了作用域,对象ret就不在了,它是一个局部对象,我们这里不能用引用,用了的话,返回的就是ret的别名,但是ret又已经销毁了,这就有可能访问野指针,所以出了作用域,如果对象不在了,就不能用引用返回,要用传值返回。
- 法二:复用日期+=天数
此种方法是建立在日期+=天数的基础上完成的,然后进行复用。
Date Date::operator+(int day) const { //法二:复用日期 += 天数 Date ret(*this); ret += day; return ret; }
日期+=天数
- 法一:
前面我们实现了日期+天数,下面我们看函数的第一行,我们就调用了一个拷贝构造:
Date ret(*this); //拷贝构造,拿d1去初始化ret
这里调用拷贝构造是为了不在*this上面直接做改动,只在ret上面进行操作,其理由是日期+天数得到的还是另外一个日期,而不用拷贝构造直接在*this上面做改动只会导致原有的日期也发生变化,而这个变化也正是我日期+=天数的需求。
Date& Date::operator+=(int day) //传引用返回 { //如果day小于0,要单独处理 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是全局的,出了作用域还在。
- 法二:复用日期+天数
Date& Date::operator+=(int day) { //法二:复用 * this = *this + day; return *this; }
⭐:法一和法二那个更优呢?
答案:法一。讨论这个问题的本质就是去分析用+去复用+=好还是用+=复用+好。答案是用+去复用+=好,因为+有两次拷贝,而+=没有拷贝,所以实现+=,并且用+去复用+=效率更高。
日期-=天数
- 思路:
日期-=天数得到的还是一个日期。日期减去天数后要分析day是否大于0,只要大于0就没问题,如果小于0就需要借位。所以我们就需要进行讨论:
- 当减的天数为负数,则调用+=
- 若减后的day<0,则从月借位
- 若月为0,则从年借位,月重置为12
- 代码如下:
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) const { Date ret(*this); ret -= day; return ret; }
前置++&&后置++
- 思路:
C++里面有前置++和后置++,这也就导致了一个问题,我们该如何区别它们呢?C++规定:无参的为前置++,有参的为后置。
- 前置++
//前置++ Date& Date::operator++() //无参的为前置 { *this += 1; //直接复用+= return *this; }
- 后置++
//后置++ Date Date::operator++(int i) //有参数的为后置 { Date tmp(*this); *this += 1; //复用+= return tmp; }
前置--&&后置--
思路:
前置--和后置--思路和前置++后置++一样。
- 前置--
//前置-- Date& Date::operator--() //无参的为前置 { *this -= 1; //直接复用-= return *this; }
- 后置--
//后置-- Date Date::operator--(int i) //有参数的为后置 { Date tmp(*this); *this -= 1; return tmp; }
日期-日期
- 思路:
日期 - 日期得到的是天数,首先我们得判断两个日期的大小,用min和max分别代表小的和大的日期,随后,算出min和max之间的差距,若min!=max,则min就++,随即定义变量n也自增++,最后返回n(注意符号)
- 代码如下:
//日期 - 日期 int Date::operator-(const Date& d) const { int flag = 1; //方便后续计算正负 Date max = *this; Date min = d; if (*this < d) { min = *this; max = d; flag = -1; //计算正负 } //确保max是大的,min是小的 int n = 0; while (min != max) { min++; n++; }//算出min和max之间绝对值差距 return n * flag; //如果d1大,结果为正,d2大结果为负 }
三、总代码
Date.h文件
#pragma once
#include <iostream>
#include <assert.h>
using std::cout;
using std::cin;
using std::endl;
class Date
{
public:
bool isLeapYear(int year) //判断是否为闰年
{
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
int GetMonthDay(int year, int month);//获取某年某月天数
//构造函数
Date(int year = 1, int month = 1, int day = 1);
//打印
void Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
//<运算符重载
bool operator<(const Date& d) const;
//==运算符重载
bool operator==(const Date& d)const;
//<=运算符重载
bool operator<=(const Date& d)const
{
return *this < d || *this == d;
}
//>运算符重载
bool operator>(const Date& d)const
{
return !(*this <= d);
}
//>=运算符重载
bool operator>=(const Date& d)const
{
return !(*this < d);
}
//!=运算符重载
bool operator!=(const Date& d)const
{
return !(*this == d);
}
Date& operator+=(int day);
Date operator+(int day) const;
Date& operator-=(int day);
Date operator-(int day) const;
//前置++
Date& operator++() //无参的为前置
{
*this += 1; //直接复用+=
return *this;
}
//后置++
Date operator++(int i) //有参数的为后置
{
Date tmp(*this);
*this += 1; //复用+=
return tmp;
}
//前置--
Date& operator--() //无参的为前置
{
*this -= 1; //直接复用-=
return *this;
}
//后置--
Date operator--(int i) //有参数的为后置
{
Date tmp(*this);
*this -= 1;
return tmp;
}
int operator-(const Date& d) const;
private:
int _year;
int _month;
int _day;
};
Date.cpp文件
#include "Date.h"
//<运算符重载
bool Date::operator<(const Date& d)const
{
if (_year < d._year ||
_year == d._year && _month < d._month ||
_year == d._year && _month == d._month && _day < d._day)
return true;
else
return false;
}
//==运算符重载
bool Date::operator==(const Date& d)const
{
return _year == d._year &&
_month == d._month &&
_day == d._day;
}
//获取某年某月天数
int Date::GetMonthDay(int year, int month)
{
assert(year >= 0 && month > 0 && month < 13);
static int monthDayArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && isLeapYear(year))
{
return 29;
}
else
{
return monthDayArray[month];
}
}
//构造函数
Date::Date(int year, int month, int day)
{
if (year >= 1 &&
month <= 12 && month >= 1 &&
day >= 1 && day <= GetMonthDay(year, month))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << "日期非法" << endl;
}
}
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;
}
Date Date::operator+(int day) const
{
//法二:复用日期 += 天数
Date ret(*this);
ret += day;
return ret;
}
//日期 -=天数 d1-=100
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) const
{
Date ret(*this);
ret -= day;
return ret;
}
//日期 - 日期
int Date::operator-(const Date& d) const
{
int flag = 1; //方便后续计算正负
Date max = *this;
Date min = d;
if (*this < d)
{
min = *this;
max = d;
flag = -1; //计算正负
} //确保max是大的,min是小的
int n = 0;
while (min != max)
{
min++;
n++;
}//算出min和max之间绝对值差距
return n * flag; //如果d1大,结果为正,d2大结果为负
}