【C++】:日期类的实现 -- 日期计算器

24k纯甄 2024-08-11 09:05:02 阅读 74

前言

1.日期类是一种十分经典的类型。对于C++的初学者,它能够帮助我们融会贯通许多C++的基础知识,它涉及许多的基础语法,比如引用,函数重载,传值/传参返回,构造函数,运算符重载,const成员等等。

如果有不了解的,可以前往我的主页浏览相关文章。

日期计算器可以实现两个日期的比较,两个日期的相减,日期的加减天数等有意义的运算。

在这里插入图片描述

2.本文依然采用多文件方式。其中:

Date.h //定义类,存放各函数的声明;

Date.cpp //实现各重载函数;

Test.cpp //测试各函数的功能。

在C++中,由于函数的声明与定义分离,如果要定义成员函数,就要指定类域,这是基本语法。

一,各个函数功能的实现

1. 检查输入的日期是否合法

不管是日期的比较还是日期的运算,第一步都要检查日期的合法性。特别是月份和每个月的天数

代码实现如下:

<code>bool Date::CheakDate()

{ -- -->

if (_month < 1 || _month>12

|| _day<1 || _day>GetMonthDay(_year, _month))

{

return false;

}

else

{

return true;

}

}

2. 构造函数 (初始化函数)

为了方便,在使用默认构造函数时,一般是自己显式的实现一个全缺省构造函数

注意:

在函数的声明和定义分离时,如果要给缺省值,必须在函数声明的时候给。

代码实现如下:

Date::Date(int year, int month, int day)

{

_year = year;

_month = month;

_day = day;

//日期的源头,是从构造函数里出来的,所以要在这里判断

if (!CheakDate())

{

cout << "日期非法!" << endl;

}

}

二,比较类的运算符重载

3. <运算符重载

判断两个日期谁更小。思路:先比年,年小就小,年相等比月,月小就小,年月相等比日,日小就小。

代码实现如下:

d1 < d2 隐含的this指针是d1,d是d2的别名

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;

}

4. ==运算符重载

判断两个日期是否相等 。这个比较简单,如果两者的年月日都相等,即相等。

代码实现如下:

bool Date::operator==(const Date& d) const

{

return _year == d._year

&& _month == d._month

&& _day == d._day;

}

5. >=运算符重载

有人可能会仿照<运算符重载的方法,使用复杂的逻辑,写各种晦涩的代码实现。其实只要实现了<运算符重载和==运算符重载,下面的日期比较类都是可以复用的。 比如这里的>=,< 取反就是>=

代码实现如下:

bool Date::operator>= (const Date& d) const

{

return !(*this < d);

}

6. >运算符重载

<= 取反,就是>。

bool Date::operator> (const Date& d) const

{

return !(*this <= d);

}

7. <=运算符重载

只要满足<或者=,就是<=。

bool Date::operator<= (const Date& d) const

{

return *this < d || *this == d;

}

8. !=运算符重载

==去取反,就是!=

bool Date::operator!=(const Date& d) const

{

return !(*this == d);

}

9. 获取某月的天数

这个函数是整个日期类的关键,也是最频繁调用的一个函数。由于这个原因,最好把它定义成内联函数,避免每次调用都要开辟空间,可以提升效率。根据C++的语法,定义在类里默认是内联,inline可加可不加

代码实现如下:

这里还有两个优化的细节:

1. month == 2 和后面的取模运算的位置。首先满足是2月,再判断是否是闰年,效率会更高。

2. static的使用,由于该函数频繁调用,把数组放在静态区,避免每次调用函数时每次都要开辟数组空间。

int GetMonthDay(int year, int month)

{

//断言,确保输入月份的有效性

assert(month > 0 && month < 13);

//枚举出月份的天数

static int monthDayArray[13] = { -1, 31,28,31,30,31,30,31,31,30,31,30,31 };

//判断2月的平年和闰年

if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))

{

return 29;

}

else

{

return monthDayArray[month];

}

}

三,运算类的重载

10. 日期+=天数

比如d1 + 50,这里的d1已经改变了

计算过程如下:

注意,每次超过月份天数后,是减当前月的天数。

在这里插入图片描述

代码实现如下:

<code>Date& Date::operator+=(int day)

{ -- -->

//这里是处理有人传负的天数,转化成调用-=函数

if (day < 0)

{

return *this -= -day;

}

//先加上天数

_day += day;

//加上天数后超出了月的范围

while (_day > GetMonthDay(_year, _month))

{

//减去当前月的天数,此时月份+1

_day -= GetMonthDay(_year, _month);

++_month;

//超过12个月时

if (_month == 13)

{

++_year;//年份+1

_month = 1;//别忘了月份还要从1月开始

}

}

return *this;

}

11. 日期 + 天数

比如 d1 + 50,d1没有改变。

代码实现如下:

Date Date::operator+(int day) const

{

//实例化一个临时的局部变量,用拷贝构造

//把d1的日期拷贝给tmp,这样d1就不会改变

Date tmp = *this;

tmp += day;//直接复用+=

//注意:出了这个函数,tmp会被销毁,所以这里不能用引用返回。

// 这里是传值返回,所以会形成一个拷贝

return tmp;

}

12. 日期-=天数

比如 d1- 50,这里的 d1也改变了。

计算过程如下:

注意,这里加(借)的是下一个月的天数。

在这里插入图片描述

代码实现如下:

<code>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;

13. 日期 - 天数

思路同 日期 + 天数。

代码实现如下:

Date Date::operator-(int day) const

{

Date tmp = *this;

tmp -= day;

return tmp;

}

四,前置,后置类的重载

首先要知道前置和后置运算的区别:

前置:返回运算后的值

后置:返回运算前的值

其次,还要理解函数重载和运算符的重载:

函数重载:可以让函数名相同,参数不同的函数存在;

运算符重载:让自定义类型可以用运算符,并且控制运算符的行为,增强可读性

这两者各论各的,没有关系。但是,多个同一运算符重载可以构成函数重载。

我们知道,前置和后置是同一运算的不同形式 ,但是他们的函数名相同,参数都是隐含的this参数,无法构成重载同时存在。所以为了区分,并且构成重载,C++规定:强行给后置(后置++和后置- -)函数的参数增加了一个 int 形参,不需要写形参名。并且这个形参没有任何意义。

14. 前置++

前置++先加,后用,返回的是加之后的值,可以直接复用+=运算符重载,返回的是改变后的 *this。

Date& Date::operator++()

{

*this += 1;

return *this;

}

15. 后置++

注意:后置函数多一个形参 int,以便与前置构成重载。

后置++是先用,后加,返回的是加之前的值。所以需要创建一个临时的局部对象 tmp,用拷贝构造把原来的 *this 拷贝给 tmp 。最后返回 tmp。

Date Date::operator++(int)

{

Date tmp = *this;

*this += 1;

return tmp;

}

16. 前置 - -

前置- -和前置++的原理类似。

Date& Date::operator--()

{

*this -= 1;

return *this;

}

17. 后置 - -

注意:后置函数多一个形参 int,以便与前置构成重载。

原理与后置++类似。

Date Date::operator--(int)

{

Date tmp(*this);

*this -= 1;

return tmp;

}

注意:

前置和后置运算,一般建议用前置,因为后置类需要拷贝构造,传值返回,这就会产生两次拷贝和一次析构,而前置却没有这样的消耗,相比之下前置类有优势

18. 日期-日期 返回天数

两个日期相减,返回的是相差的天数,是一个整形。

思路:找出大的年份和小的年份,再定义一个计数器和小的年份一起++,直到和大的年份相等。

比如 d1 - d2

代码实现如下:

//隐含的this指针是d1,d是d2的别名

int Date::operator-(const Date& d) const

{

//先假设大日期和小日期

Date max = *this;

Date min = d;

//默认假设正确

int flag = 1;

//如果假设错误,就进行改正

if (*this < d)

{

max = d;

min = *this;

flag = -1;

}

int n = 0;

//让计数n和小的日期一起加,加到和大的日期相等

while (min != max)

{

++min;

++n;

}

//flag的正负有妙用

return n * flag;

}

五,完整代码

Date.h

#pragma once

#include <iostream>

using namespace std;

#include <assert.h>

#include <stdbool.h>

class Date

{

//构造函数

Date(int year, int month, int day);

void Print() const;

//定义为内联函数

int GetMonthDay(int year, int month)

{

//断言,确保输入月份的有效性

assert(month > 0 && month < 13);

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;

}

else

{

return monthDayArray[month];

}

}

//检查日期的合法性

bool CheakDate();

//两个日期之间的比较

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;

//d1 += 100,d1已经改变

Date& operator+=(int day);

Date& operator-=(int day);

//d1 + 50,d1不变

Date operator+(int day) const;

Date operator-(int day) const;

// d1 - d2

int operator-(const Date& d) const;

// ++d1 -> d1.operator++()

Date& operator++();

// d1++ -> d1.operator++(1) 整数任意给

Date operator++(int);

//前置,后置--

Date& operator--();

Date operator--(int);

private:

int _year;

int _month;

int _day;

};

Date.cpp

#define _CRT_SECURE_NO_WARNINGS

#include "Date.h"

bool Date::CheakDate()

{

if (_month < 1 || _month>12

|| _day<1 || _day>GetMonthDay(_year, _month))

{

return false;

}

else

{

return true;

}

}

//1.缺省参数只能在声明的时候给

//2.成员函数声明与定义分离时,要指定类域

Date::Date(int year, int month, int day)

{

_year = year;

_month = month;

_day = day;

if (!CheakDate())

{

cout << "日期非法!" << endl;

}

}

void Date::Print() const

{

cout << _year << "-" << _month << "-" << _day << endl;

}

//思路:先比年,年小就小,年相等比月,月小就小,年月相等比日,日小就小

//d1 <d2 隐含的this是d1, d是d2的别名

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;

}

//先写好大于和等于 或者 小于和等于的函数,其余的进行复用

//d1 <=d2

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);

}

//日期 += 天数 :d1 + 100

//这里的d1 已经改了

Date& Date::operator+=(int day)

{

//这里是处理有人传负的天数

if (day < 0)

{

return *this -= -day;

}

//先加上天数

_day += day;

//加上天数后超出了月的范围

while (_day > GetMonthDay(_year, _month))

{

//减去当前月的天数,此时月份+1

_day -= GetMonthDay(_year, _month);

++_month;

//超过12个月时

if (_month == 13)

{

++_year;//年份+1

_month = 1;//别忘了月份还要从1月开始

}

}

return *this;

}

// d1 + 50,d1没有改变

Date Date::operator+(int day) const

{

//实例化一个临时的局部变量,用拷贝构造,把d1的日期拷贝给tmp,这样d1就不会改变

Date tmp = *this;

tmp += day;//直接复用+=

//注意:出了这个函数,tmp会被销毁,所以这里不能用引用返回。

// 这里是传值返回,所以会形成一个拷贝

return tmp;

}

//日期 -= 天数 :d1 - 100

//这里的d1 已经改了

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 tmp = *this;

tmp -= day;

return tmp;

}

// ++d

Date& Date::operator++()

{

*this += 1;

return *this;

}

//这两种++ 建议用前置++,因为后置++会产生两次拷贝和一次析构,相比之下前置++有优势。

//d++

Date Date::operator++(int)

{

Date tmp = *this;

*this += 1;

return tmp;

}

//--d

Date& Date::operator--()

{

*this -= 1;

return *this;

}

//d--

Date Date::operator--(int)

{

Date tmp(*this);

*this -= 1;

return tmp;

}

//思路:找出大的年份和小的年份,再定义一个计数器和小的年份一起++,直到和大的年份相等。

//d1 - d2

int Date::operator-(const Date& d) const

{

Date max = *this;

Date min = d;

int flag = 1;

if (*this < d)

{

max = d;

min = *this;

flag = -1;

}

int n = 0;

while (min != max)

{

++min;

++n;

}

return n * flag;

}

Test.cpp

#define _CRT_SECURE_NO_WARNINGS

#include "Date.h"

void TestDate1()

{

Date d1(2024, 4, 14);

Date d2 = d1 + 5000;

d1.Print();

d2.Print();

Date d3(2024, 4, 14);

Date d4 = d3 - 5000;

d3.Print();

d4.Print();

Date d5 (2024, 4, 14);

d5 += -5000;//转化成-=运算,计算5000天之前的时间

d5.Print();

}

void TestDate2()

{

Date d1(2024, 4, 14);

Date d2 = ++d1;

d1.Print();

d2.Print();

Date d3 = d1++;

d1.Print();

d3.Print();

}

int main()

{

TestDate1();

return 0;

}

日期的比较类比较简单,不在这里示范,读者自行验证。

比如,调用TestDate1()计算未来的日期和以前的日期:

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/75d98d13cd1e42aab9a5aebe8d0468f9.png

再比如,调用TestDate2()观察前置与后置运算的区别:

在这里插入图片描述



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。