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

发布于:2024-05-05 ⋅ 阅读:(40) ⋅ 点赞:(0)

前言

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

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

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

在这里插入图片描述
2.本文依然采用多文件方式。其中:

Date.h //定义类,存放各函数的声明;
Date.cpp //实现各重载函数;
Test.cpp //测试各函数的功能。

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

一,各个函数功能的实现

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

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

代码实现如下:

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已经改变了
计算过程如下:
注意,每次超过月份天数后,是减当前月的天数。
在这里插入图片描述

代码实现如下:

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也改变了。
计算过程如下:
注意,这里加(借)的是下一个月的天数。
在这里插入图片描述
代码实现如下:

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()观察前置与后置运算的区别:

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到