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

news2024/11/19 17:42:32

前言

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

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1636820.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python 与 TensorFlow2 生成式 AI(五)

原文&#xff1a;zh.annas-archive.org/md5/d06d282ea0d9c23c57f0ce31225acf76 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第十二章&#xff1a;用生成式人工智能玩视频游戏&#xff1a;GAIL 在之前的章节中&#xff0c;我们已经看到如何使用生成式人工智能来生成…

如何在Linux上安装Python?2024Python安装教程

在Linux上安装Python并不难&#xff0c;对于Ubuntu或Debian系统&#xff0c;使用命令sudo apt install python3&#xff1b;对于CentOS、Red Hat或Fedora系统&#xff0c;使用命令sudo yum install python3。 如何在Linux上安装Python&#xff1f; 确切的安装步骤有所不同&am…

Django后台项目开发实战七

为后台管理系统换风格 第七阶段 安装皮肤包 pip install django-grappelli 在 setting.py 注册 INSTALLED_APPS [grappelli,django.contrib.admin,django.contrib.auth,django.contrib.contenttypes,django.contrib.sessions,django.contrib.messages,django.contrib.stat…

微隔离实施五步法,让安全防护转起来

前言 零信任的最核心原则→最小权限 安全的第一性原理→预防 零信任的最佳实践→微隔离 “零信任”这个术语的正式出现&#xff0c;公认是在2010年由Forrester分析师John Kindervag最早提出。时至今日&#xff0c;“零信任”俨然已成安全领域最热门的词汇&#xff0c;做安全…

如何使用Go语言进行并发安全的数据访问?

文章目录 并发安全问题的原因解决方案1. 使用互斥锁&#xff08;Mutex&#xff09;示例代码&#xff1a; 2. 使用原子操作&#xff08;Atomic Operations&#xff09;示例代码&#xff1a; 3. 使用通道&#xff08;Channels&#xff09; 在Go语言中&#xff0c;进行并发编程是常…

《QT实用小工具·四十九》QT开发的轮播图

1、概述 源码放在文章末尾 该项目实现了界面轮播图的效果&#xff0c;包含如下特点&#xff1a; 左右轮播 鼠标悬浮切换&#xff0c;无需点击 自动定时轮播 自动裁剪和缩放不同尺寸图片 任意添加、插入、删除 单击事件&#xff0c;支持索引和自定义文本 界面美观&#xff0c;圆…

遥感雷达波段的原理及应用

雷达波段是不同波长的组。每一种都有其独特的穿透地球表面的能力。它们还可以揭示环境的不同方面。 雷达频段在电磁频谱内具有特定的频率范围。这些波段由 L-、S-、C- 和 X-波段等字母表示。稍后会详细介绍这一点。 什么是合成孔径雷达&#xff1f; 合成孔径雷达 (SAR) 是一…

C语言实验-循环结构和选择结构

一&#xff1a; 求和:1(14)(149)(14916)…(14916…n2)? 其中n的值由键盘输入&#xff1b; #define _CRT_SECURE_NO_WARNINGS #include<stdio.h>int main() {int sum 0;int n 0;printf("请输入一个整数");scanf("%d", &n);for (int i 0; i &l…

MATLAB 字符串

MATLAB 字符串 在MATLAB中创建字符串非常简单。实际上&#xff0c;我们已经使用了很多次。例如&#xff0c;您在命令提示符下键入以下内容- 示例 my_string ‘(cainiaojc.com)’ MATLAB将执行上述语句并返回以下结果 my_string (cainiaojc.com) MATLAB将所有变量视为数组&a…

Python基础学习之记录中间文件

倘若想记录代码运行过程中的结果文件&#xff0c;那么以下函数仅供参考 代码示例&#xff1a; import os import datetime import sys import pandas as pd# 定义总的文件夹路径 base_folder E:\\D\\log\\product_data_compare_log# 定义一个函数来创建带时间戳的文件夹 def…

特征提取(Feature Extraction)常见统计特征笔记(三)

统计特征是描述数据集中值的一组量&#xff0c;通常用于了解数据的分布、集中趋势和变异程度。常见的统计特征包括均值、中位数、众数、标准差、方差等。下面会详细解释每个统计特征&#xff0c;并给出相应的Python代码。 1、均值&#xff08;Mean&#xff09;&#xff1a;所有…

【团体程序设计天梯赛】往年关键真题 L2-036 网红点打卡攻略 模拟 L2-037 包装机 栈和队列 详细分析完整AC代码

【团体程序设计天梯赛 往年关键真题 详细分析&完整AC代码】搞懂了赛场上拿下就稳 【团体程序设计天梯赛 往年关键真题 25分题合集 详细分析&完整AC代码】&#xff08;L2-001 - L2-024&#xff09;搞懂了赛场上拿下就稳了 【团体程序设计天梯赛 往年关键真题 25分题合…

【webrtc】MessageHandler 4: 基于线程的消息处理:以Fake 收发包模拟为例

G:\CDN\rtcCli\m98\src\media\base\fake_network_interface.h// Fake NetworkInterface that sends/receives RTP/RTCP packets.虚假的网络接口,用于模拟发送包、接收包单纯仅是处理一个ST_RTP包 消息的id就是ST_RTP 类型,– 然后给到目的地:mediachannel处理: 最后消息消…

沟通是SAP项目成功的关键

我在前面的文章中提到SAP项目并不是传统意义上的IT项目&#xff0c;因为SAP项目实施的不仅仅是一个简单的ERP系统&#xff0c;除了系统之外还有流程再造、组织结构变更、用户培训等。在实施过程中有很多部门都要参与进来讨论和做决定&#xff0c;有很多问题和冲突需要解决。从关…

LangChain入门2 RAG详解

RAG概述 一个典型的RAG应用程序,它有两个主要组件&#xff1a; 索引&#xff1a;从源中获取数据并对其进行索引的管道。这通常在脱机情况下发生。检索和生成&#xff1a;在运行时接受用户查询&#xff0c;并从索引中检索相关数据&#xff0c;然后将其传递给模型。 从原始数据…

透明加密的解释及意义,透明加密软件有哪些?

一、什么是透明加密及意义 透明数据加密&#xff08;Transparent Data Encryption (简称TDE)&#xff09;是指可以在文件层对数据和文件进行实时加密和解密&#xff0c;落盘的文件是加密后的内容&#xff0c;而对于上层应用系统和开发人员而言&#xff0c;加解密过程是无感知的…

Python_GUI框架 PyQt 与 Pyside6的介绍

Python_GUI框架 PyQt 与 Pyside6的介绍 一、简介 在Python的GUI&#xff08;图形用户界面&#xff09;开发领域&#xff0c;PyQt和PySide6是两个非常重要的工具包。它们都基于Qt库&#xff0c;为Python开发者提供了丰富的GUI组件和强大的功能。当然Python也有一些其他的GUI工…

Python 绘图边缘留白问题解决方案 / plt.savefig / plt.subplots_adjust 函数简析

文章目录 Part.I IntroductionPart.II 解决方案Chap.I plt.savefigChap.II plt.subplots_adjustChap.III plt.margins Part.III 探索历程Chap.I 默认保存方式Chap.II 使用 bbox_inches 参数控制Chap.III 自定义留白 Reference Part.I Introduction Python 绘图默认保存&#x…

【DeepL】菜鸟教程:如何申请DeepL免费API并使用Python的DeepL

前言 在这篇技术博文中,我们将介绍如何利用DeepL的强大功能,通过其免费API在Python项目中实现高质量的文本翻译。我们将从基础开始,解释DeepL是什么,它的用途,如何申请免费API,以及如何在Python中使用DeepL库。 什么是DeepL? DeepL是一个基于人工智能的翻译服务,它以…

开通Jetbrains个人账号,赠送这些付费插件

开通Jetbrains个人账号&#xff0c;或者Jetbrains现成账号的, 可赠送以下付费插件 现成账号&#xff1a;https://web.52shizhan.cn/activity/xqt8ly 个人账号&#xff1a;https://web.52shizhan.cn/legal 账号支持全家桶系列&#xff1a;AppCode,CLion,DataGrip,GoLand,Intell…