文章目录
前言
路漫漫其修远兮,吾将上下而求索;
一、导言
首先,要实现日期类,构造函数是一定是要我们显式实现的,但因为日期类的成员变量均为内置类型且不涉及资源,所以其拷贝构造函数、赋值运算符重载函数、析构函数均无需我们显式实现,编译器自动生成的便够用;以及还需要我们实现日期类相关的功能,例如:日期相减、日期加天数等;
注:
1、类中成员函数的声明和定义分离时,在定义该函数的时候需要指定类域
2、缺省参数只能在声明给,在定义中不给
此处日期类的实现,我们会分文件,将类的主体放在Date.h 之中,将日期类中成员函数的定义放到Date.cpp 中;
二、构造
在Date.h 中代码如下:
#pragma once
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1);
private:
int _year;
int _month;
int _day;
};
在Date.cpp 中代码如下:
#include"Date.h"
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
此外我们再增加一个打印函数:
Date.h 中代码如下:
#pragma once
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1);
void Print();
private:
int _year;
int _month;
int _day;
};
Date.cpp 中代码如下:
#include"Date.h"
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Date::Print()
{
cout << _year << '/' << _month << '/' << _day << endl;
}
三、比较大小
我们要实现大于、大于等于、小于、小于等于、等于、不等于的运算符重载函数
Date.h 中代码如下:
#pragma once
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1);
void Print();
//比较大小
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);
private:
int _year;
int _month;
int _day;
};
1、实现大于
依次比较年、月、日
年大日期就大,当年相等时比较月,月大日期就大,当月相等就比较日,日大日期就大;
代码如下了:
//*this > d
bool Date::operator>(const Date& d)
{
if (_year > d._year)
{
return true;
}
else if(_year == d._year)
{
if (_month > d._month)
{
return true;
}
else if (_month == d._month)
{
if (_day > d._day)
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
return false;
}
简化代码:
bool Date::operator>(const Date& d)
{
if (_year > d._year)
{
return true;
}
else if (_year == d._year && _month > d._month)
{
return true;
}
else if (_year == d._year && _month == d._month && _day > d._day)
{
return true;
}
return false;
}
2、等于
对于比较大小的逻辑,向来时先实现大于、等于或者小于、等于;那么剩下的比较逻辑就可以复用这两个先实现的;
代码如下:
bool Date::operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
3、大于等于
直接复用大于和等于的逻辑
代码如下:
bool Date::operator>=(const Date& d)
{
return (*this) > d || (*this) == d;
}
4、小于
小于就是大于等于的相反逻辑
代码如下:
bool Date::operator<(const Date& d)
{
return !(*this >= d);//利用逻辑取反
}
5、小于等于
复用小于和等于
代码如下:
bool Date::operator<=(const Date& d)
{
return (*this < d) || (*this == d);
}
6、不等于
等于的相反逻辑
代码如下:
bool Date::operator!=(const Date& d)
{
return !(*this == d);
}
二、加减
1、加与加等
此处的加与加等只能日期+天数,日期+=天数,因为日期加日期是没有意义的;
Date.h 中的代码如下:
#pragma once
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1);
void Print();
//比较大小
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);
//加与加等
Date operator+(int day);
Date& operator+=(int day);
private:
int _year;
int _month;
int _day;
};
Q1:日期加一个天数该如何相加;
- 相加未超过该月天数,直接相加
- 相加超过该月天数:进位(天满了往月上进,月满了往年上进)
日期+天数的本质:加法的进位
想要进位,首先就得知道当前月份有多少天,需要单独写一个函数结合年月日来获得该月的天数,为这个函数命名为 GetMonthDay ;
GetMonthDay的实现:
- 方法一:使用switch case , 也可以使用 if else 来划分月数,唯独2月的时候需要判断当前年是否为闰年
- 方法二:定义一个数组,利用数组下标去映射月份,改下标对应的空间就是该月份的天数,需要对二月进行特殊处理:判断当前年是否为闰年;
注:闰年二月29天,平年二月28天;
GetMonthDay的参考代码如下:
//inline 在类中定义的函数默认为内联函数
int GetMonthDay(int year, int month, int day)
{
static int monthDayArray[13] = { -1 , 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 monthDayArray[month];
}
函数GetMonthDay 直接放到Date.h 中类Date 的Public中便可,因为函数GetMonthDay会被频繁使用并且代码行数不多,写成内联挺友好的;
Q:为什么要在数组monthDayArray 前面添加一个 static ?
- 因为函数GetMonthDay会被频繁调用,而每次均会使用该数组而开辟空间(有时间上的消耗),故而完全可以将该数组放到静态区中;
例如 d1 + 20 是不会改变d1 中的数据所以可以加上const 来修饰this指针 ,在实现的过程中需要创建跟d1 一样的局部对象,要返回结果,所以返回类型为Date;
我们先实现出来,代码如下:
//加
Date Date::operator+(int day)
{
//创建局部对象
Date tmp(*this);
tmp._day += day;
//要让_day 符合当前月份的天数
while(tmp._day > GetMonthDay(tmp._year, tmp._month))
{
//大于,tmp._day -当前月份天数 , 然后月份+1
tmp._day -= GetMonthDay(tmp._year, tmp._month);
++tmp._month;
//还要判断月份是否越界
if (tmp._month > 12)
{
++tmp._year;
tmp._month = 1;
}
}
return tmp;
}
//加等
Date& Date::operator+=(int day)
{
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month > 12)
{
++_year;
_month = 1;
}
}
return *this;
}
简单测试一下:
这个小测试没有问题,但是并不代表这个代码没有问题,可以多测试几次,分析此处容易出bug 的地方,跨闰年、跨许多月份;
但是我们也不知道从2025年5月5日过5000天便是2039年1月12日是否正确,如何解决这个问题呢?
- 方法一:一次性不加这么多,逐步叠加,需要手算
- 方法二:日期相加是一个很简单、成熟的程序,可以借助别人的程序加以验证(上网搜)
可见,我们的代码主体逻辑上是没有问题的;
倘若加负数呢?
加上负数就出现了问题,在代码实现的时,首先应该判断day 是正数还是负数;
代码如下:
//加
Date Date::operator+(int day)
{
//创建局部对象
Date tmp(*this);
if (day < 0)
return tmp -= (-day);//复用减等
tmp._day += day;
//要让_day 符合当前月份的天数
while(tmp._day > GetMonthDay(tmp._year, tmp._month))
{
//大于,tmp._day -当前月份天数 , 然后月份+1
tmp._day -= GetMonthDay(tmp._year, tmp._month);
++tmp._month;
//还要判断月份是否越界
if (tmp._month > 12)
{
++tmp._year;
tmp._month = 1;
}
}
return tmp;
}
//加等
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 > 12)
{
++_year;
_month = 1;
}
}
return *this;
}
当day 为负数的时候,直接去复用 -= 的函数
其实还可以继续优化,让加复用加等,因为加和加等是一样的逻辑,如下:
//加
Date Date::operator+(int day)
{
//创建局部对象
Date tmp(*this);
//复用加等
tmp += day;
return tmp;
}
此处就不测试了,实现了 -= 的重载再测试;
对比加与加等:
相似的主逻辑的提取:
实际上就有两种复用方式:
方式一:加复用加等
方式二:加等复用加
Q: 这两种复用方式哪一种更好?
- 加复用加等更好;
2、减与减等
加是进位,减是借位;进位是减去当前月份的天数,然后再让当前月份进位;而借位则就需要加上当前月上一个月的天数,如何实现?
- 先处理月份,然后再处理天数;
减的实现的代码如下:
//减
Date Date::operator-(int day)
{
Date tmp(*this);
tmp._day -= day;
//判断tmp._day 是否合法
while(tmp._day <= 0)
{
//是要获取上一个月的天数,所以月份先减
--tmp._month;
if (tmp._month == 0)
{
--tmp._year;
tmp._month = 12;
}
tmp._day += GetMonthDay(tmp._year, tmp._month);
}
return tmp;
}
减等实现的代码如下:
//减等
Date& Date::operator-=(int day)
{
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
测试一下:
用现成的日期计算器验证一下:
同理,我们也可以以复用的形式实现,并且考虑减负数的情况:
减等的参考代码如下:
//减等
Date& Date::operator-=(int day)
{
//day 有可能为负数
if (day < 0)
return (*this) += (-day);
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
减的参考代码如下:
//减 - 复用减等
Date Date::operator-(int day)
{
Date tmp(*this);
tmp -= day;
return tmp;
}
3、++、--
++、-- 为一元操作符,而一元操作符是不会区分该操作数位于操作符的左边还是位于操作符的右边,只有二元操作符会区分左、右操作数;而++、-- 分为前置++、后置++,前置--、后置--,所以不能通过操作数在++、-- 的左边还是右边来进行区分,++、--只是一元操作符;
C++规定,后置++、-- 重载的时候可以增加一个int 形参,跟前置++、--进行区分;而至于该形参,可以给值,也可以不给值,因为这个形参的存在只是为了支持前置与后置的重载,不会使用到这个形参,即这个形参在其实现的内部逻辑中没有任何作用,所以给不给值都无所谓;
Date.h 中的声明如下:
#pragma once
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1);
void Print();
//inline 在类中定义的函数默认为内联函数
int GetMonthDay(int year, int month)
{
static int monthDayArray[13] = { -1 , 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 monthDayArray[month];
}
//比较大小
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);
//加与加等
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);
private:
int _year;
int _month;
int _day;
};
注:
- 1、虽然C++规定后置需要在形参部分增加一个int 加以区分,实际上还可以使用其他类型,eg.char 、double……
- 2、前置是先自增(自减)然后再返回,所以前置使用引用返回;
- 3、可以复用前面写的加、加等、减、减等
++的参考代码:
//++
//前置 - 先自增后使用
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;
}
测试:
4、日期-日期
日期不可以直接减,因为每个月得天数不一样;
方法一:灵活处理,算该日期与日期之间年月日的差距,可以是先算月和日的差距然后再算年的;
eg. 2025.5.5 到 2004.1.1 相差了多少天
方法二:通过比较(假设法)得到两个日期中的较大的日期与较小的日期,让较小的日期不断地去++,再此过程中还要利用计数器计数,当较小日期等于较大日期的时候,计数器中的值就是这两个日期相差的天数;当然,如果两个日期相差地特别大的时候,该方法的效率便低了些(计算机的计算效率非常快),但终归是一个方法;
需要注意大日期-小日期以及小日期-大日期的情况,大日期-小日期得到的是正数,小日期-大日期得到的是负数,在代码实现的时候可以增加一个变量flag 来表示 this 指向的日期是大日期还是小日期;
在Date.h 中的声明:
#pragma once
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1);
void Print();
//inline 在类中定义的函数默认为内联函数
int GetMonthDay(int year, int month)
{
static int monthDayArray[13] = { -1 , 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 monthDayArray[month];
}
//比较大小
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);
//加与加等
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);
//日期-日期
//*this - d
int operator-(const Date& d);
private:
int _year;
int _month;
int _day;
};
参考代码:(写于Date.cpp 中)
//日期-日期
//*this - d
int Date::operator-(const Date & d)
{
int flag = 1;
//先利用假设法求得两个日期中的较大值
Date max = *this;
Date min = d;
if (*this < d)
{
max = d;
min = *this;
flag = -1;
}
//计数器记录
int count = 0;
while (min < max)
{
++min;
++count;
}
return flag*count;
}
利用别人实现的日期计算器验证一下:
Q:为什么使用前置++?
- 早期认为内置类型对象的前置++(--) 效率比后置++(--) 的效率要高,但是现在由于计算机的发展,可以认为内置类型对象的前置++(--) 和后置++(--) 没有区别;但是对于自定义类型,前置++(--) 的效率要比后置++(--) 的效率高,这是因为前置++(--) 的底层没有任何的拷贝,但是后置++(--) 存在两个拷贝;
三、流提取、流插入
- 重载<< 和 >> 的时候,需要重载为全局函数,因为重载为成员函数,this 指针会默认抢占第一个形参的位置,而重载函数要求第一个参数得是左侧得运算对象,调用时则成了 对象<<cout , 不符合我们得使用习惯和可读性;于是重载 << 和 >> 需要放在全局,把 ostream/istream 放在第一个形参的位置上,第二个形参位置上放置当前类类型的对象;
Q1:为什么存在流插入、流提取这么抽象的东西?
- C语言中的printf 和 scanf 由于占位符的存在,是有不足的,它只能输入输出内置类型的数据,而不能输出内置类型的数据;也不能直接输出类Date中的年月日(因为类Date 的成员变量是私有的,只有在类Date 中提供Printf 这样的成员函数才可以直接访问私有成员变量);C++想着把内置类型与自定义类型串在一起,让它们可以一起解决,此时便提出了流插入、流提取;流插入、流提取的存在使得任何类型的数据均可输出,只不过对于内置类型的数据来说 ,系统库已经写好了,我们可以直接用,并且其自动识别类型是因为其构成了函数重载;
Q2:这样做会不会导致效率降低?
- 相比C语言会有一些效率上的降低,但是影响不大,一般情况下我们不用担心这样的问题,因为CPU的速度是非常快的;现在CPU的每秒的计算速度差不多都是几十亿、几百亿次;
像这种使用cout 进行连续的打印,不能用我们实现的Print 函数来混用,所以需要我们自己来实现类Date 的流插入与流提取;
1、流插入
Q:什么是 cout?
cout 其实是库中一个叫ostream 类型的一个对象,ostream实际叫做:basic_ostream<char> , 经过typedef 所以称为ostream--> typedef basic_ostream<char> ostream;
ostream 即 out stream 输出流;
而之所以cout 可以自动识别内置类型的数据,是因为内置类型的数据已经被重载好了,并构成哈数重载;
我们需要思考,运算符<<的重载放在哪里?全局还是Date类中?有几个参数?放在全局的话,如何在外部获得类Date 中的私有成员变量?
我们先简单实现再测试,然后一步一步完善:
注:重载运算符函数的参数应该和该运算符作用的运算符对象的数量一样多。一元运算符有一个操作数,故而其重载的函数只有一个形参,二元运算符有两个操作数,故而该运算符重载函数有两个形参,并且其第一个形参为该运算符的左操作数,第二个形参为该运算符的右操作数;
所以我们可以根据报错得到两点:
- 1、<< 的重载函数第一个形参是ostream类型的对象,其第二个参数是Date 类型的对象(这是不能将<< 的重载函数写做Date 成员函数的主要原因,参数不匹配)
- 2、要在类外获取类Date 中的私有成员变量;
但是如果我们就是要将<< 的重载实现在类Date 中呢?这样就不用担心在类外获取类中私有成员变量的问题了;
测试如下:
运算符重载本身就是为了增强代码的可读性,本来应该写成 cout<<d1; 但是经过重载之后就要写成 d1<<cout; 显然这样会导致代码的可读性下降,并且不支持连续地写;可读性这么差,还不如使用Print ;还有就是,不能交换定义,即定义">>" 和cout 一起使用,写成 d1>>cout; 规定了<< 为流插入操作符,>> 为流提取操作符;就算可以这么做,那么内置类型的数据又怎么办?
所以,流插入的重载不能为成员函数,必须写成全局的函数;因为流插入的左操作数为cout,其运算符重载函数的第一个参数必须是 ostream类型,而成员函数的第一个参数默认为this 指针,为当前类的类型,这是矛盾的;只有放成全局函数,但是放在全局又有一个问题:要在类外访问类中的私有成员变量;
在上一篇类和对象(二)中曾提到,想要访问类中私有的成员变量有三种方式:
在此处,方法三不可行,那么就有两种方式:
- 方法一:友元函数
- 方法二:间接访问(利用GetYear、GetMonth、GetDay)
方法二在此处可行,本文采用友元函数的形式;给类中的函数添加一个友元声明,那么该函数不属于这个类,但是却可以访问到该类中的私有成员变量;
使用如下:
简单测试一下:
有时候我们会连续输出,继续测试:
连续赋值是从右往左结合,与赋值不同的是,连续流插入是从左往右结合:
d1先流插入,插入之后返回一个对象以作为下一次流插入的左操作数,那么显然,我们实现的流插入的重载函数的返回值应该是cout , 让cout 继续去做下一次流插入;
Q:像这种连续的流插入,为什么他能够自动识别类型且能识别不同的类型?
- 本质上是多个函数的调用;
需要在类Date中进行友元声明,加上一个关键字frined 即可:
代码如下:
//流插入 - 写在全局
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
测试:
2、流提取
流提取即在流中去获取数据(输入),cin 是 istream类型的对象;
对于流提取也同理,流提取重载函数要实现在全局,并且要声明为类Date 的友元函数,需要注意的是其两个形参均不能添加const ,一是会可能修改流对象,而是会把流中的数据放入该类对象中;
Q1:C语言中的scanf 要取变量的地址,为什么要取其地址呢?
- 因为scanf 就是标准输入流(stdin) 从控制台(键盘)获取数据,以后要将数据放入地址所对应的空间之中;如果不取地址,eg. scanf("%d",i); 假设有个形参x,将实参i 传给形参(传值调用),改变形参而不会影响实参,那么输入就是无效的,所以需要传址调用,即需要取地址;
而在C++ 中有了引用的概念是不需要传地址的;
需要在类Date中进行友元声明,加上一个关键字frined 即可:
代码:
//流提取 - 写在全局
istream& operator>>(istream& in, Date& d)
{
cout << "请依次输入年、月、日:>";
in >> d._year >> d._month >> d._day;
return in;
}
测试如下:
注:cin 在输入的时候默认是用空格或者换行符进行分割的;
从底层来看,cout 与 cin 本质上是一样的,均是IO行为,类似于读文件,控制台(屏幕)也是一种文件(内存文件,并非磁盘中的文件)
有一点bug,我们可以输入不错误的日期,如下:
Q:如何解决非法日期的输入呢?
- 需要在输入的时候对日期进行检查;
假设用C语言结构体实现此类,是控制不了的,但是C++可以,可以通过控制构造函数与流插入的实现即可;因为构造函数实质上就是对变量的初始化,而流插入输入数据,只要在数据来源上保证日期合法,并且其他函数的实现均是正确的,那么便不会出现非法日期;倘若我们控制了构造函数和流插入的实现,但还是存在非法日期,那么就是程序中的bug;
日期检查的大体思路:(假设对年不做检查)
声明:
参考代码:
bool Date::CheckDate()
{
//月份不小于1,不大于12
if (_month < 1 || _month>12
|| _day <1 || _day > GetMonthDay(_year, _month))
{
return false;
}
else
{
return true;
}
}
Q:为什么对年不做检查?
- 对于年来说,一般是公元后或者公元前,公元前不太合理,因为历史上日期的实际上是更改过的,尤其是星期。有些地方是跟不上的。即我们当前4年一闰百年不闰的日期是一千多年前的时候才确定的,古代对于地球公转、自转的观察是不够的,故而历史上有日期会跳过几天以修正,而农历又是根据节气制定的;
Q:为什么4年一闰,百年不闰,而400年又一闰?
- 地球自转一周的时间为一天,地球围绕太阳公转一圈便是一年;但是实际上地球公转的时间为365天5小时48分46秒,那么每年就会多差不多6小时,4年就会多一天,所以4年一闰;但是实际上按6小时算一年还是少了12分钟左右,四年就是48分钟,那么25个四年可以认为多了一天(实际上是多了18~20小时),所以100年不闰;但是每过一百年就又会少4~6小时,所以400又一闰,补上少的这一天;
构造函数的优化,代码如下:
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
//优化:检查输入的日期是否合法
if (!CheckDate())
{
cout << "日期非法:" << *this << endl;
}
}
注:在成员函数中访问类中的成员(成员函数、成员变量),会在该成员前默认添加一个this 指针,此 CheckDate 是this 指针调用的,即调用当前构造函数的这个对象调用了CheckDate;
测试一下:
流提取的优化:
//流提取 - 写在全局
istream& operator>>(istream& in, Date& d)
{
while (1)
{
cout << "请依次输入年、月、日:>";
in >> d._year >> d._month >> d._day;
//优化 - 判断输入的日期是否合法
if (d.CheckDate())
{
break;
}
else
{
cout << "输入的日期非法,请重新输入" << endl;
}
}
return in;
}
测试:
四、日期类所有代码汇总
优化:可以在不会改变this 指针执行对象的内容的函数后面加上const
Date.h 中的代码如下:
#pragma once
#include<iostream>
using namespace std;
class Date
{
//友元函数
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in , Date& d);
public:
Date(int year = 1, int month = 1, int day = 1);
void Print()const;
bool CheckDate()const;
//inline 在类中定义的函数默认为内联函数
int GetMonthDay(int year, int month)const
{
static int monthDayArray[13] = { -1 , 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 monthDayArray[month];
}
//比较大小
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 day)const;
Date& operator+=(int day);
//减与减等
Date operator-(int day)const;
Date& operator-=(int day);
//++
//前置 - 先自增后使用
Date& operator++();
//后置 - 先使用后自增
Date operator++(int);
//--
//前置 - 先自减后使用
Date& operator--();
//后置 - 先使用后自减
Date operator--(int);
//日期-日期
//*this - d
int operator-(const Date& d) const;
private:
int _year;
int _month;
int _day;
};
Date.cpp 中的代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
//检查日期是否合法
bool Date::CheckDate() const
{
//月份不小于1,不大于12
if (_month < 1 || _month>12
|| _day <1 || _day > GetMonthDay(_year, _month))
{
return false;
}
else
{
return true;
}
}
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
//优化:检查输入的日期是否合法
if (!CheckDate())
{
cout << "日期非法:" << *this << endl;
}
}
void Date::Print() const
{
cout << _year << '/' << _month << '/' << _day << endl;
}
//比较大小
bool Date::operator>(const Date& d) const
{
if (_year > d._year)
{
return true;
}
else if (_year == d._year && _month > d._month)
{
return true;
}
else if (_year == d._year && _month == d._month && _day > d._day)
{
return true;
}
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) || (*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 day)
{
if (day < 0)
return *this -= (-day);//复用减等
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month > 12)
{
++_year;
_month = 1;
}
}
return *this;
}
//加
Date Date::operator+(int day) const
{
//创建局部对象
Date tmp(*this);
//复用加等
tmp += day;
return tmp;
}
//减等
Date& Date::operator-=(int day)
{
//day 有可能为负数
if (day < 0)
return (*this) += (-day);
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
//减 - 复用减等
Date Date::operator-(int day) const
{
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;
}
//日期-日期
//*this - d
int Date::operator-(const Date & d) const
{
int flag = 1;
//先利用假设法求得两个日期中的较大值
Date max = *this;
Date min = d;
if (*this < d)
{
max = d;
min = *this;
flag = -1;
}
//计数器记录
int count = 0;
while (min < max)
{
++min;
++count;
}
return flag*count;
}
//流插入 - 写在全局
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
//流提取 - 写在全局
istream& operator>>(istream& in, Date& d)
{
while (1)
{
cout << "请依次输入年、月、日:>";
in >> d._year >> d._month >> d._day;
//优化 - 判断输入的日期是否合法
if (d.CheckDate())
{
break;
}
else
{
cout << "输入的日期非法,请重新输入" << endl;
}
}
return in;
}
总结
可以尝试自己写一下,有利于掌握类和对象的知识,细节挺多的;