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

前言

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 - d2int 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.mzph.cn/pingmian/5521.shtml

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

相关文章

【Python小练】求斐波那契数列第n个数

题目 输出斐波那契数列第n个数。 分析 首先我们要知道&#xff0c;斐波那契数列&#xff0c;这个数列从第三位开始等于前两个数的和&#xff0c;要知道数列第n个数&#xff08;n>2&#xff09;&#xff0c;就要知道其前两相的值&#xff0c;着就需要用到递归了。来看一下吧…

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…

Apache中如何配置 ws 接口

Apache中如何配置 wss 接口 在Apache中配置WebSockets的支持&#xff0c;你需要使用mod_proxy_wstunnel模块&#xff0c;该模块是Apache的一个代理模块&#xff0c;它允许你代理WebSocket请求。 以下是配置步骤的简要说明和示例&#xff1a; 确保你的Apache服务器安装了mod_…

【最大公约数 排序】2344. 使数组可以被整除的最少删除次数

本文涉及知识点 最大公约数 排序 LeetCode2344. 使数组可以被整除的最少删除次数 给你两个正整数数组 nums 和 numsDivide 。你可以从 nums 中删除任意数目的元素。 请你返回使 nums 中 最小 元素可以整除 numsDivide 中所有元素的 最少 删除次数。如果无法得到这样的元素&a…

【高质量】2024五一数学建模C题保奖思路+代码(后续会更新)

你的点赞收藏是我后续更新的最大动力&#xff01; 一定要点击文末的卡片&#xff0c;那是获取资料的入口&#xff01; 你是否在寻找数学建模比赛的突破点&#xff1f; 作为经验丰富的数学建模团队&#xff0c;我们将为你带来2024 年五一数学建模&#xff08;C题&#xff09;…

1700java进销存管理系统Myeclipse开发sqlserver数据库web结构java编程计算机网页项目

一、源码特点 java web进销存管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为sqlser…

Vue通过下拉框选择字典值,并将对应的label以及value值提交到后端

产品品种从字典中获取 产品性质也是从字典中获取 字典当中的保存 dict_type表 dict_data表 在表单提交的方法中 1.因为做的产品性质是多选&#xff0c;它会以数组的方式提交&#xff0c;所以需要先将Json格式转变为String JSON.stringify(this.form.nature) 2.提交表单&…

【Linux网络编程】DNS、ICMP、NAT技术、代理服务器+网络通信各层协议总结

DNS、ICMP、NAT技术、代理服务器网络通信总结 1.DNS2.ICMP协议2.1ping命令2.2traceroute命令 3.NAT技术4.NAT和代理服务器5.网线通信各层协议总结 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&…

vue3 依赖-组件tablepage-vue3版本1.0.3更新内容

github求⭐ 可通过github 地址和npm 地址查看全部内容 vue3 依赖-组件tablepage-vue3说明文档&#xff0c;列表页快速开发&#xff0c;使用思路及范例-汇总 vue3 依赖-组件tablepage-vue3说明文档&#xff0c;列表页快速开发&#xff0c;使用思路及范例&#xff08;Ⅰ&#…

JAVAEE—servlet的概念及使用,使用servlet接口实现一个表白墙

文章目录 servlet的概念静态页面和动态页面servlet的作用 写出一个servlet程序目录的创建设置smart tomcat编写helloworld servlet的概念 首先我们要搞明白什么是servlet&#xff0c;servlet是一种实现动态页面的技术&#xff0c;他是由tomcat提供给程序员的一组API可以帮助程…

【学习笔记二十九】EWM较特殊的业务场景

一、供应商寄售业务相关 1.创建寄售物料、寄售信息记录以及寄售的采购订单 2.创建交货单 3.维护入库交货 行项目里存在C寄售的标识 4.创建上架的仓库任务并确定 查看仓位库存&#xff0c;发现仓位库存里存在寄售标识C以及寄售库存所有方 5.寄售转自有 ①首先MIGO里进行寄…

【吊打面试官系列】Java高并发篇 - Java 中的同步集合与并发集合有什么区别?

大家好&#xff0c;我是锋哥。今天分享关于 【Java 中的同步集合与并发集合有什么区别&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; Java 中的同步集合与并发集合有什么区别&#xff1f; 同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合&…

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

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

想要接触网络安全,应该怎么入门学习?

作为一个网络安全新手&#xff0c;首先你要明确以下几点&#xff1a; 我刚入门网络安全&#xff0c;该怎么学&#xff1f;要学哪些东西&#xff1f;有哪些方向&#xff1f;怎么选&#xff1f;这一行职业前景如何&#xff1f; 其次&#xff0c;如果你现在不清楚学什么的话&…

物联网实战--平台篇之(二)基础搭建

目录 一、Qt工程创建 二、数据库知识 三、通信协议 四、名词定义 本项目的交流QQ群:701889554 物联网实战--入门篇https://blog.csdn.net/ypp240124016/category_12609773.html 物联网实战--驱动篇https://blog.csdn.net/ypp240124016/category_12631333.html 一、Qt工程…

如何使用ChatGPT进行高效的中文到科学英文翻译?

如何使用ChatGPT进行高效的中文到科学英文翻译 在全球化加速的今天&#xff0c;科学交流往往需要跨越语言障碍。特别是在科研领域&#xff0c;有效地将中文研究成果转化为精准的科学英语描述&#xff0c;对于学术发表和国际合作尤为关键。AI翻译工具如ChatGPT可以在这一过程中…

ubuntu入门

基础命令 cd 切换命令 ls 查看当前目录下所有的文件 cp a.c b.c 拷贝a.c 到 b.c touch a.c 创建a.c文件 mkdir file 创建文件夹file rm file 删除文件 rmdir 删除test文件夹 rmdir test/ mv 移动文件 mv a.c b.c 把a.c 替换成b.c ifconfig 查看电脑网络信息 rm xx 删…

虹科Pico汽车示波器 | 免拆诊断案例 | 起动机免拆诊断故障 2 例

电磁开关、换向器烧蚀及炭刷磨损均会导致起动机偶尔不工作&#xff0c;使发动机偶尔无法起动。由于故障是偶发的&#xff0c;且没有故障代码&#xff0c;这往往会让维修人员无从下手&#xff0c;而用Pico示波器测量起动电流&#xff0c;就会让这些“亚健康状态”一目了然。 案例…

MongoDB磁盘空间占满,导致数据库被锁定,如何清理数据和磁盘空间

一、问题 1、我在实际项目中&#xff0c;遇到一个问题&#xff0c;随着数据每天的不断增加&#xff0c;导致mongodb的磁盘空间站满了&#xff0c;数据库被锁了&#xff0c;无法使用。 2、故障表现 部署的应用程序突然无法将数据写入数据库&#xff0c;但是可以正常读取数据。…

与 Apollo 共创生态:观看7周年大会的心路历程

前言 在科技飞速发展的今天&#xff0c;自动驾驶技术已然成为行业创新的热点之一。作为一名长期关注自动驾驶领域的技术人员&#xff0c;我有幸见证了Apollo平台的成长与壮大。七年前&#xff0c;Apollo的诞生为我们带来了无尽的想象与期待&#xff1b;七年后的今天&#xff0…