目录
1. 日期类设计思路
日期是一个看似简单但细节很多的类型。月有长短、闰年二月特殊、跨年进位……这些规则让日期类成为练习C++类设计的绝佳案例。
本文实现了一个完整的class Date,支持:
-
日期合法性校验
-
日期 ± 天数
-
两个日期相减(得到天数差)
-
前置/后置自增自减
-
全部比较运算符
-
流输入输出
2. 核心内容与功能
该日期类采用年、月、日三个成员变量实现。
成员变量:
class Date
{
private:
int _year;
int _month;
int _day;
};
2.1 构造、析构与校验
默认构造函数:由于成员变量中不涉及资源空间的开辟,因此我们可以选择不写默认构造函数。但由于编译器对于内置类型的变量初始化是未定义的,为了严谨起见,这里手动实现一个全缺省的默认构造函数:
// 全缺省的构造函数
// 声明
Date(int year = 1900, int month = 1, int day = 1);
// 定义
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
if(!CheckDate())
{
cout << "输入的日期非法!" << endl;
}
}
注意:声明中需要具体写缺省值,定义中不能重复写,仅需写对应类型和形参。
拷贝构造函数:可以选择不写,还是由于不涉及资源空间开辟的原因。但是以练习为目的的话还是推荐自己手动实现一下的:
// 拷贝构造函数d2(d1)
// 声明
Date(const Date& d);
// 定义
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
说一下拷贝函数内部什么时候需要确认是否为自拷贝:
如果涉及资源空间的开辟,一定要检验拷贝不是自拷贝。
因为拷贝涉及对原有空间释放和获取拷贝源头数据的操作,自拷贝会导致释放了自己的空间后又试图找到并拷贝这个空间,最终导致程序崩溃。
析构函数:可以选择不写,依旧是因为不涉及资源空间的开辟:
// 析构函数
// 声明
~Date();
// 定义
Date::~Date()
{
_year = 0;
_month = 0;
_day = 0;
}
校验日期的合法性:
如何判断这个日期是否合法?
方便起见,我们抛开年份不谈(笔者不太清楚历史TvT),直接对月份和日期进行校验。
通过月份判断天数是否合法,容易想到可以先用数组实现一个获取天数的函数:把月份作为下标,通过访问下标获取天数,然后可以据此判断日期的合法性。
对于获取天数的函数,声明和定义都放在类内实现,默认是inline函数。这个函数经常使用且代码长度较短,符合inline函数标准。另外,用static把数组放在静态区可以减少不必要的数组创建和销毁,提高效率。
// 获取某年某月的天数
int GetMonthDay(int year, int month) const
{
assert(month > 0 && month < 13);
static int MonthDay[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;
}
return MonthDay[month];
}
// 判断日期合法
// 声明
bool CheckDate() const;
// 定义
bool Date::CheckDate() const
{
if(_month < 1 || _month > 12)
{
return false;
}
if(_day < 1 || _day > GetMonthDay(_year, _month))
{
return false;
}
return true;
}
2.2 日期运算
在计算日期前,我们需要对以下运算符进行重载:
- + :日期加天数,返回新的日期,原日期不改变
- - :日期减天数,返回新的日期,原日期不改变;日期减日期,返回相差的天数
- += :日期加天数,返回新的原日期
- - = :日期减天数,返回新的原日期
方便的是,只要实现了+=和-=,那么+和-可以复用这些操作符,大大减少了代码量。
计算方面,可以运用上之前获取天数的方法,通过类似加法进位,减法退位的方法计算日期。
注意:如果涉及原日期的修改,需要传引用返回;不需要修改,则需要传值返回。
Q:为什么不是+=和-=复用+和-?
A:因为+和-涉及到类对象的拷贝,会降低效率;而+=和-=是直接对对象的引用进行操作的,不会影响效率。
另外,如果日期是非法的,那么就不能进行运算了。因此在运算前记得检查一下日期的合法性😎
声明:
// 日期+=天数
Date& operator+=(int day);
// 日期-=天数
Date& operator-=(int day);
// 日期+天数
Date operator+(int day);
// 日期-天数
Date operator-(int day);
// 日期-日期 返回天数
int operator-(const Date& d);
定义:
// 日期+=天数
Date& Date::operator += (int day)
{
if(!CheckDate())
{
cout << "日期非法,无法运算!" << endl;
return *this;
}
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)
{
if(!CheckDate())
{
cout << "日期非法,无法运算!" << endl;
return *this;
}
if(day < 0)
{
return *this += (-day);
}
_day -= day;
while(_day <= 0)
{
_day += GetMonthDay(_year, _month);
_month--;
if(_month == 0)
{
_year--;
_month = 12;
}
}
return *this;
}
// 日期+天数
Date Date::operator + (int day)
{
Date tmp = *this;
tmp += day;
return tmp;
}
// 日期-天数
Date Date::operator - (int day)
{
Date tmp = *this;
tmp -= day;
return tmp;
}
// 日期-日期 返回天数
int Date::operator - (const Date& d)
{
int flag = 1;
Date big = *this;
Date small = d;
if(*this < d)
{
big = d;
small = *this;
flag = -1;
}
int Nday = 0;
while(small != big)
{
small++;
Nday++;
}
return Nday * flag;
}
2.3 日期自增自减
自增自减涉及到前置运算符和后置运算符。由于运算符重载时第一个参数不需要显式传,自增自减又是一元运算符,会导致无法区分前置和后置。
因此C++规定,前置运算符重载时不传参,后置运算符重载需要传一个占位int类型来区分;
同时,由于前置先自增后使用的特性,可以直接传引用返回;后置自增则需要拷贝一次对象,对象自增后返回拷贝值,可以确保在使用后再自增,因此需要传值返回。
自减同理。
上述可见,在使用时更推荐前置自增自减,可以省去拷贝步骤,提高效率😎
// 前置++
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;
}
2.4 各类比较运算符
有了日期运算的经验,我们可以在重载各类比较运算符时进行复用,比如手动实现了>和==的函数逻辑,就可以在>=,<=,<等复用了,省时省力🤩
不过至少要自己实现两个函数,切忌全部复用!否则就会出现你复用我,我复用你的无限递归情况,比如:

这里 >= 和 < 互相复用,就会陷入无限递归了TvT
// >运算符重载
bool Date::operator > (const Date& d)
{
if(_year > d._year)
{
return true;
}
if(_year == d._year && _month > d._month)
{
return true;
}
if(_month == d._month && _day > d._day)
{
return true;
}
return false;
}
// ==运算符重载
bool Date::operator == (const Date& d)
{
if(_year == d._year && _month == d._month && _day == d._day)
{
return true;
}
return false;
}
// >=运算符重载
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));
}
2.5 输入输出流
为了方便日期的输入输出,这里还需要对运算符<<和>>进行重载。
但是,如果重载函数放在类内部,而类内部又默认第一个参数是类类型的引用,那么就会出现一个非常诡异的使用方式:




离谱的是,这种反直觉的设计是可以正常运行的,不过由于可读性极差,不支持链式调用等一系列问题,非常不推荐写成这种形式。因此,这里推荐一个更正规的写法:设置为全局函数
把运算符重载函数放在全局声明,并在类内部(位置不限)声明友元函数即可,这下就实现了和普通cin / cout一样的功能了:支持连续输入输出,使用起来和平时一样顺手🤓
//类内部友元函数声明
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
//输入输出(全局声明)
ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);
//输入输出(全局)
ostream& operator << (ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日";
return out;
}
istream& operator >> (istream& in, Date& d)
{
cout << "请依次输入年月日:" << endl;
while(1)
{
in >> d._year >> d._month >> d._day;
if(!d.CheckDate())
{
cout << "输入日期" << d << "非法,请重新输入!" << endl;
}
else
{
break;
}
}
return in;
}
3. 代码实现
以下是所有代码整合:
Date.h文件
#pragma once
#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) const
{
assert(month > 0 && month < 13);
static int MonthDay[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;
}
return MonthDay[month];
}
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1);
// 拷贝构造函数
// d2(d1)
Date(const Date& d);
// 析构函数
~Date();
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& operator=(const Date& d);
//判断日期合法
bool CheckDate() const;
//打印日期
void Print() const;
// 日期+=天数
Date& operator+=(int day);
// 日期-=天数
Date& operator-=(int day);
// 日期+天数
Date operator+(int day);
// 日期-天数
Date operator-(int day);
// 前置++
Date& operator++();
// 后置++
Date operator++(int);
// 前置--
Date& operator--();
// 后置--
Date operator--(int);
// >运算符重载
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;
};
//输入输出(全局)
ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);
Date.cpp文件
#include"Date.h"
// 全缺省的构造函数(参数只能给在声明中!)
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
if(!CheckDate())
{
cout << "输入的日期非法!" << endl;
}
}
// 拷贝构造函数
// d2(d1)
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
// 析构函数
Date::~Date()
{
_year = 0;
_month = 0;
_day = 0;
}
// 赋值运算符重载(如果开辟了资源需要先释放)
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
//判断日期合法
bool Date::CheckDate() const
{
if(_month < 1 || _month > 12)
{
return false;
}
if(_day < 1 || _day > GetMonthDay(_year, _month))
{
return false;
}
return true;
}
//打印日期
void Date::Print() const
{
if(!CheckDate())
{
cout << "日期非法,无法打印!" << endl;
return;
}
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
// 日期+=天数
Date& Date::operator += (int day)
{
if(!CheckDate())
{
cout << "日期非法,无法运算!" << endl;
return *this;
}
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)
{
if(!CheckDate())
{
cout << "日期非法,无法运算!" << endl;
return *this;
}
if(day < 0)
{
return *this += (-day);
}
_day -= day;
while(_day <= 0)
{
_day += GetMonthDay(_year, _month);
_month--;
if(_month == 0)
{
_year--;
_month = 12;
}
}
return *this;
}
// 日期+天数
Date Date::operator + (int day)
{
Date tmp = *this;
tmp += day;
return tmp;
}
// 日期-天数
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;
}
// >运算符重载
bool Date::operator > (const Date& d)
{
if(_year > d._year)
{
return true;
}
if(_year == d._year && _month > d._month)
{
return true;
}
if(_month == d._month && _day > d._day)
{
return true;
}
return false;
}
// ==运算符重载
bool Date::operator == (const Date& d)
{
if(_year == d._year && _month == d._month && _day == d._day)
{
return true;
}
return false;
}
// >=运算符重载
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));
}
// 日期-日期 返回天数
int Date::operator - (const Date& d)
{
int flag = 1;
Date big = *this;
Date small = d;
if(*this < d)
{
big = d;
small = *this;
flag = -1;
}
int Nday = 0;
while(small != big)
{
small++;
Nday++;
}
return Nday * flag;
}
//输入输出(全局)
ostream& operator << (ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日";
return out;
}
istream& operator >> (istream& in, Date& d)
{
cout << "请依次输入年月日:" << endl;
while(1)
{
in >> d._year >> d._month >> d._day;
if(!d.CheckDate())
{
cout << "输入日期" << d << "非法,请重新输入!" << endl;
}
else
{
break;
}
}
return in;
}
如果对你有帮助,还请多多支持~[]~( ̄▽ ̄)~*我们下篇博客见
//封面是saki酱🥰

1002

被折叠的 条评论
为什么被折叠?



