【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…

【Kotlin】select简介

1 前言 协程的 select 是一种用于异步操作的选择器&#xff0c;它允许同时等待多个挂起函数的结果&#xff0c;并在其中一个完成时执行相应的操作。 能够被 select 的事件都是 SelectClause&#xff0c;在 select.kt 中有定义&#xff0c;如下。 public interface SelectBuild…

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

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

react中useReducer如何使用

useReducer 是 React 提供的一个用于状态管理的 Hook&#xff0c;它接收一个 reducer 函数和初始状态作为参数&#xff0c;并返回当前状态以及一个 dispatch 函数。这个 Hook 适用于管理复杂或嵌套的状态对象&#xff0c;它提供了一种更加结构化的更新状态的方法。 useReducer是…

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; 你的支持是对我最大的鼓励&…

【Python】指定正负样本在逻辑回归和随机森林模型训练中的重要性

太多的借口 太多的理由 为了爱情 我也背叛了所有 如果你想离开我 就别再畏畏缩缩 太多的借口 太多的理由 别再问我难过时候怎么过 或许会好好地活 或许会消失无踪 你在乎什么 &#x1f3b5; 陈冠蒲《太多》 在机器学习的分类问题中&#xff0c;正确地指定…

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可以帮助程…

好用的电子文档管理系统应具备哪些功能?

文档是我们工作生活的基本组成部分&#xff0c;但你可能没有注意到花了多少时间处理它们。 良好的电子文档管理可以节省时间并简化整个组织的流程&#xff0c;来进一步了解下吧&#xff01; 文档管理软件的用例 电子文档管理系统,8Manage EDMS,高亚科技 文档管理不仅仅是组…

html中引用视频文件的方式有哪些?

在HTML中引用视频文件的方式有以下几种&#xff1a; 使用 <video> 标签&#xff1a;通过使用 <video> 标签来嵌入视频文件&#xff0c;可以设置视频的大小、播放控件和其他属性。示例代码如下&#xff1a; <video src"video.mp4" controls width&qu…

信息安全实战01_基于AES(CMAC)加密算法脚本开发

本文框架 前言1. 基于AES128的CMAC认证脚本开发1.1 hex文件解析1.2 涉及的Python库1.3 读取待认证的hex数据1.4 计算CMAC1.5 CMAC填充1.6 Chechsum的计算前言 在信息安全中有两个非常重要的应用:安全启动及安全刷新,所谓安全启动即是在用户程序启动前,先进行校验,防止恶意…

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

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

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

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

C语言-宏定义2.0

无参宏 这些宏没有参数&#xff0c;它们只是简单地将一个特定的文本替换为另一个文本 无参宏意味着使用宏的时候&#xff0c;无需指定任何参数&#xff0c;比如&#xff1a; #define PI 3.14 #define SCREEN_SIZE 800*480*4 int main() {// 在代码中&#xff0c;可以…

【团体程序设计天梯赛】往年关键真题 L2-033 简单计算器 栈 L2-035 完全二叉树的层序遍历 树 详细分析完整AC代码

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