C++自定义日期类的精彩之旅(详解)

        

        在学习了C++的6个默认成员函数后,我们现在动手实现一个完整的日期类,来加强对这6个默认成员函数的认识。
        这是日期类中所包含的成员函数和成员变量:

构造函数

// 函数:获取某年某月的天数
inline int GetMonthDay(int year, int month)
{// 静态数组,存储普通年份每个月的天数。注意:数组索引0未使用,实际月份从1开始static int dayArray[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };// 默认情况下,直接从数组中获取对应月份的天数int day = dayArray[month];// 特殊情况处理:如果是二月(即month == 2),并且year是闰年if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))){// 闰年的二月有29天day = 29;}// 返回计算得到的该月天数return day;
}// 类Date的构造函数,用于初始化一个日期对象
Date::Date(int year, int month, int day)
{// 检查传入的年、月、日是否构成一个合法的日期if (year >= 0    // 年份需为非负数&& month >= 1 && month <= 12  // 月份需在1-12之间&& day >= 1 && day <= GetMonthDay(year, month))  // 日需在1-该月最大天数之间{// 如果合法,则设置日期成员变量的值_year = year;_month = month;_day = day;}else{// 如果日期不合法,这里简单通过控制台输出错误信息// 更严谨的做法应该是抛出一个异常,让调用者来决定如何处理这个错误cout << "非法日期" << endl;cout << year << "年" << month << "月" << day << "日" << endl;}
}

GetMonthDay函数中的三个细节:

  • 该函数可能被多次调用,所以我们最好将其设置为内联函数。
  • 函数中存储每月天数的数组最好是用static修饰,存储在静态区,避免每次调用该函数都需要重新开辟数组。
  • 逻辑与应该先判断month == 2是否为真,因为当不是2月的时候我们不必判断是不是闰年。

注意:当函数声明和定义分开时,在声明时注明缺省参数,定义时不标出缺省参数。 

打印函数

// 打印函数
void Date::Print() const
{cout << _year << "年" << _month << "月" << _day << "日" << endl;
}

日期and天数

日期 += 天数

        对于+=运算符,我们先将需要加的天数加到日上面,然后判断日期是否合法,若不合法,则通过不断调整,直到日期合法为止。

调整日期的思路:

  • 若日已满,则日减去当前月的天数,月加一。
  • 若月已满,则将年加一,月置为1。
  • 反复执行1和2,直到日期合法为止。
// 重载运算符 '+=',使Date对象可以加上一个整数天数
Date& Date::operator+=(int day)
{// 如果要添加的天数是负数,转换成减法操作并调用operator-=,以复用已实现的逻辑if (day < 0){// 通过将负数转正并调用减法操作符来实现减去天数的功能*this -= -day; }else{// 直接将天数加到当前日期的天数上_day += day;// 确保累加后的日期是合法的,如果不是,则逐步调整至合法日期while (_day > GetMonthDay(_year, _month)){// 若累加后超过当月天数,从下个月的天数中继续累加_day -= GetMonthDay(_year, _month);// 进入下一个月_month++;// 如果月份超过12,则年份增加,并将月份重置为1if (_month > 12){_year++;_month = 1;}}}// 返回当前对象的引用,支持链式赋值return *this;
}

 注意:当需要加的天数为负数时,转而调用-=运算符重载函数。

日期 + 天数

        +运算符的重载,我们可以复用上面已经实现的+=运算符的重载函数。

        但要注意:虽然我们返回的是加了之后的值,但是对象本身的值并没有改变。就像a = b + 1,b + 1的返回值是b + 1,但是b的值并没有改变。所以我们还可以用const对该函数进行修饰,防止函数内部改变了this指针指向的对象。

// 重载运算符 '+',允许Date对象与一个表示天数的整数相加,生成一个新的Date对象
Date Date::operator+(int day) const
{// 创建当前日期对象的一个副本tmp,避免修改原始对象的值Date tmp(*this); // 使用拷贝构造函数创建副本// 复用已实现的operator+=方法,将天数加到tmp上tmp += day; // 返回累加天数后的新日期对象return tmp;
}

注意:+=运算符的重载函数采用的是引用返回,因为出了函数作用域,this指针指向的对象没有被销毁。但+运算符的重载函数的返回值只能是传值返回,因为出了函数作用域,对象tmp就被销毁了,不能使用引用返回。 

日期 -= 天数

        对于-=运算符,我们先用日减去需要减的天数,然后判断日期是否合法,若不合法,则通过不断调整,直到日期合法为止。

调整日期的思路:

  • 若日为负数,则月减一。
  • 若月为0,则年减一,月置为12。
  • 日加上当前月的天数。
  • 反复执行1、2和3,直到日期合法为止。
// 重载运算符 '-=', 允许Date对象减去一个表示天数的整数,并直接修改当前对象的日期
Date& Date::operator-=(int day)
{if (day < 0){// 如果要减去的天数是负数,则转换为加上一个正数天数,复用operator+=方法*this += -day; }else{// 直接减去天数_day -= day;// 确保日期合法性:如果_day小于等于0,需要向前调整月份乃至年份while (_day <= 0){// 减少月份,若月份变为0,则同时减少年份并设置月份为12(上年的12月)_month--;if (_month == 0){_year--;_month = 12;}// 根据调整后的年月获取该月的实际天数,并累加到_day中,直至_day为正数,确保日期有效_day += GetMonthDay(_year, _month);}}// 返回当前对象的引用,以便支持链式赋值return *this;
}

 注意:当需要减的天数为负数时,转而调用+=运算符重载函数。

日期 - 天数

        和+运算符的重载类似,我们可以复用上面已经实现的-=运算符的重载函数,而且最好用const对该函数进行修饰,防止函数内部改变了this指针指向的对象。

// 重载运算符 '-',允许Date对象减去一个表示天数的整数,并返回一个新的Date对象表示结果
Date Date::operator-(int day) const
{// 创建当前日期对象的一个副本tmp,以避免修改原始对象Date tmp(*this); // 利用拷贝构造函数创建副本// 复用已实现的operator-=方法,从副本tmp中减去指定的天数tmp -= day; // 返回减去天数后的新日期对象return tmp;
}

注意:-=运算符的重载函数采用的是引用返回,但-运算符的重载函数的返回值只能是传值返回,也是由于-运算符重载函数中的tmp对象出了函数作用域被销毁了,所以不能使用引用返回。 

前置and后置

前置 ++

        前置++,我们可以复用+=运算符的重载函数。

// 重载前置自增运算符 '++',使Date对象自身向前推进一天并返回修改后的对象
Date& Date::operator++()
{// 直接复用已实现的operator+=方法,将当前日期对象增加一天*this += 1; // 返回经过自增操作后的当前对象(即指向自身的引用)return *this;
}

后置 ++

        由于前置++和后置++的运算符均为++,为了区分它们的运算符重载,我们给后置++的运算符重载的参数加上一个int型参数,使用后置++时不需要给这个int参数传入实参,因为这里int参数的作用只是为了跟前置++构成重载。

// 重载后置自增运算符 '++',使Date对象自身向前推进一天,并返回自增前的日期对象
Date Date::operator++(int)
{// 创建当前日期对象的一个副本tmp,用于保存自增前的日期Date tmp(*this); // 使用拷贝构造函数创建副本// 复用已实现的operator+=方法,将当前日期对象增加一天*this += 1; // 返回自增操作前的日期对象tmpreturn tmp;
}

注意:后置++也是需要返回加了之前的值,只能先用对象tmp保存之前的值,然后再然对象加一,最后返回tmp对象。由于tmp对象出了该函数作用域就被销毁了,所以后置++只能使用传值返回,而前置++可以使用引用返回。 

前置 --

        复用前面的-=运算符的重载函数。

// 重载前置自减运算符 '--',使Date对象自身向后回退一天并返回修改后的对象
Date& Date::operator--()
{// 直接复用已实现的operator-=方法,将当前日期对象减去一天(即向前一天)*this -= 1; // 返回经过自减操作后的当前对象(即指向自身的引用)return *this;
}

后置--

// 重载后置自减运算符 '--',使Date对象自身向后回退一天,并返回回退前的日期对象
Date Date::operator--(int)
{// 创建当前日期对象的一个副本tmp,用来保存自减操作前的日期Date tmp(*this); // 利用拷贝构造函数创建副本// 复用已实现的operator-=方法,将当前日期对象减去一天(即向后一天)*this -= 1; // 返回自减操作执行前的日期对象tmpreturn tmp;
}

日期类的大小关系比较

        日期类的大小关系比较需要重载的运算符看起来有6个,实际上我们只用实现两个就可以了,然后其他的通过复用这两个就可以实现。

注意:进行日期的大小比较,我们并不会改变传入对象的值,所以这6个运算符重载函数都应该被const所修饰。

>运算符的重载

        >运算符的重载很简单,先判断年是否大于,再判断月是否大于,最后判断日是否大于,这其中有一者为真则函数返回true,否则返回false。

// 重载大于比较运算符 '>', 用于比较两个Date对象的大小
bool Date::operator>(const Date& d) const
{// 首先比较年份,如果当前对象的年份大于参数对象的年份,则当前对象更大,返回trueif (_year > d._year){return true;}// 如果年份相同,则继续比较月份else if (_year == d._year){// 当前对象的月份大于参数对象的月份,则当前对象更大,返回trueif (_month > d._month){return true;}// 如果月份也相同,则继续比较日else if (_month == d._month){// 当前对象的日大于参数对象的日,则当前对象更大,返回trueif (_day > d._day){return true;}}}// 如果以上条件都不满足,说明当前对象不大于参数对象,返回falsereturn false;
}

==运算符的重载

        ==运算符的重载也是很简单,年月日均相等,则为真。

// 重载等于比较运算符 '==', 用于判断两个Date对象是否表示相同的日期
bool Date::operator==(const Date& d) const
{// 同时检查年、月、日是否分别相等return (_year == d._year)          // 年份相等&& (_month == d._month)     // 月份相等&& (_day == d._day);       // 日相等// 所有条件都满足时,认为两个Date对象表示相同的日期,返回true// 否则,返回false
}

>=运算符的重载

// 重载大于等于比较运算符 '>=', 判断当前Date对象是否大于或等于另一个Date对象
bool Date::operator>=(const Date& d) const
{// 使用逻辑或运算符检查当前对象是否大于(使用已重载的>运算符)或等于(使用已重载的==运算符)参数对象dreturn (*this > d) || (*this == d);// 如果任何一个条件为真(即当前对象大于d,或者等于d),则返回true,表示当前对象大于等于d// 否则,返回false
}

<运算符的重载

// 重载小于比较运算符 '<', 判断当前Date对象是否小于另一个Date对象
bool Date::operator<(const Date& d) const
{// 通过逻辑非操作符'!'来反转大于等于运算的结果,从而判断当前对象是否小于参数对象dreturn !(*this >= d);// 如果当前对象不大于等于d(即不等于d也不大于d),则返回true,表示当前对象小于d// 否则,返回false
}

<=运算符的重载

// 重载小于等于比较运算符 '<=', 判断当前Date对象是否小于或等于另一个Date对象
bool Date::operator<=(const Date& d) const
{// 通过逻辑非操作符'!'来反转大于运算的结果,从而判断当前对象是否小于或等于参数对象dreturn !(*this > d);// 如果当前对象不大于d(即小于d或等于d),则返回true,表示当前对象小于等于d// 否则,返回false
}

!=运算符的重载

// 重载不等于比较运算符 '!=', 判断当前Date对象是否与另一个Date对象表示不同的日期
bool Date::operator!=(const Date& d) const
{// 通过逻辑非操作符'!'来反转等于运算的结果,从而判断当前对象是否不等于参数对象dreturn !(*this == d);// 如果当前对象不等于d(即年、月、日中有任一不同),则返回true,表示两个日期不同// 否则,返回false 表示两个日期相同
}

日期 - 日期

        日期 - 日期,即计算传入的两个日期相差的天数。我们只需要让较小的日期的天数一直加一,直到最后和较大的日期相等即可,这个过程中较小日期所加的总天数便是这两个日期之间差值的绝对值。若是第一个日期大于第二个日期,则返回这个差值的正值,若第一个日期小于第二个日期,则返回这个差值的负值。

// 重载减法运算符 '-', 用于计算两个Date对象之间的天数差
int Date::operator-(const Date& d) const
{// 初始化两个临时Date对象,假设*this为较大日期(max),d为较小日期(min)Date max = *this;Date min = d;// 初始化标记变量flag为1,表示差值预期为正int flag = 1;// 如果假设错误,即*this实际上小于d,则交换max和min,并将flag设为-1以表示差值应为负if (*this < d){max = d;min = *this;flag = -1;}// 初始化计数器n用于记录累加的总天数int n = 0;// 当较小的日期(min)不等于较大的日期(max)时,持续累加天数while (min != max){// 将较小的日期增加一天min++; // 总天数累加n++;}// 最终返回n乘以flag,以确保正确的正负符号,即两个日期之间的实际天数差return n * flag;
}

        代码中使用flag变量标记返回值的正负,flag为1代表返回的是正值,flag为-1代表返回的是负值,最后返回总天数与flag相乘之后的值即可。

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

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

相关文章

常见磁盘分区问题

给磁盘分区有几个主要的原因&#xff1a; 组织和管理数据&#xff1a;分区可以帮助用户更好地组织和管理数据。例如&#xff0c;你可以在一个分区上安装操作系统&#xff0c;而在另一个分区上存储个人文件。这样&#xff0c;即使操作系统崩溃或需要重新安装&#xff0c;你的个…

Docker 使用 Fedora 镜像

Fedora 在 Docker 中的使用也非常简单&#xff0c;直接使用命令 docker run -it fedora:latest bash 就可以 pull 到本地的容器中并且运行。 C:\Users\yhu>docker run -it fedora:latest bash Unable to find image fedora:latest locally latest: Pulling from library/fed…

【瑞萨RA6M3】2. UART 实验

https://blog.csdn.net/qq_35181236/article/details/132789258 使用 uart9 配置 打印 void hal_entry(void) {/* TODO: add your own code here */fsp_err_t err;uint8_t c;/* 配置串口 */err g_uart9.p_api->open(g_uart9.p_ctrl, g_uart9.p_cfg);while (1){g_uart9.…

mysql的隔离性——MVCC

MVCC通过undolog版本链和readview来实现 更新和删除时会写入undolog中。 读已提交&#xff1a;在事务任意读时创建readview&#xff0c;读最新提交的事务 可重复读&#xff1a;在事务第一次读时创建readview

使用Caché管理工具

Cach通过一个web工具来对其进行系统管理和完成管理任务,该方法的一个好处是不必将Cach安装到用于管理的系统上。目前,通过网络远程管理和控制对站点的访问,这些都比较容易。因为数据及其格式信息都直接来自被管理的系统,因此,这也可以最小化跨版本的兼容问题。 本文将描述…

Kubernetes二进制(单master)部署

文章目录 Kubernetes二进制&#xff08;单master&#xff09;部署一、常见的K8S部署方式1. Minikube2. Kubeadmin3. 二进制安装部署4. 小结 二、K8S单&#xff08;Master&#xff09;节点二进制部署1. 环境准备1.1 服务器配置1.2 关闭防火墙1.3 修改主机名1.4 关闭swap1.5 在/e…

(done) 关于 pytorch 代码里常出现的 batch_first 到底是啥?

参考文章&#xff1a;https://pytorch.org/docs/stable/generated/torch.nn.utils.rnn.pad_sequence.html 首先看参考文章里的解释&#xff0c;如下图 从文章描述来看&#xff0c;当 batch_first True 时&#xff0c;输出的张量的 size 是 B x T x *。当 batch_first False…

umi搭建react项目

UMI 是一个基于 React 的可扩展企业级前端应用框架&#xff0c;提供路由、状态管理、构建和部署等功能&#xff0c;可以帮助开发者快速构建复杂的单页面应用&#xff08;SPA&#xff09;和多页面应用&#xff08;MPA&#xff09;。它与 React 的关系是&#xff0c;UMI 构建在 R…

移动端自动化测试工具 Appium 之 main 启动

文章目录 一、背景二、生成xml文件2.1、创建xml方法2.2、执行主类MainTest2.3、自动生成的xml2.4、工程目录2.5、执行结果 三、命令行执行appium服务四、主方法启动类五、集成Jenkins六、总结 一、背景 Jenkins 做集成测试是不错的工具&#xff0c;那么UI自动化是否可以&#…

图解自动驾驶中的运动规划(Motion Planning),附几十种规划算法

目录 1 自动驾驶驶向何处&#xff1f;2 什么是运动规划&#xff1f;3 运动规划实战教程4 加入我们5 订阅需知 1 自动驾驶驶向何处&#xff1f; 自动驾驶&#xff0c;又称无人驾驶&#xff0c;是依靠计算机与人工智能技术在没有人为操纵的情况下&#xff0c;完成完整、安全、有效…

2.1.2 事件驱动reactor的原理与实现

LINUX 精通 2 day14 20240513 day15 20240514 算法刷题&#xff1a;2维前缀和&#xff0c;一二维差分 耗时 135min 习题课 4h 课程补20240425 耗时&#xff1a;4h 课程链接地址 回顾 怎么学0voice课网络io——一请求一线程&#xff0c;一个client一个连接再accpet分配io f…

linux系统修改网卡名称

说明&#xff1a; 因操作过程需要停用网卡&#xff0c;导致ssh远程连接不上&#xff0c;需要控制台登录操作。 测试环境&#xff1a; CentOS7.9、8.2虚拟机 Suse15 SP4虚拟机 操作步骤&#xff1a; 方法一&#xff1a; 1、 查看网卡当前名称及状态 ip a2、 将网卡状态从启用…

RK3566(泰山派):GP7101背光驱动

RK3566&#xff08;泰山派&#xff09;&#xff1a;GP7101背光驱动 文章目录 RK3566&#xff08;泰山派&#xff09;&#xff1a;GP7101背光驱动GP7101背光驱动电路配置i2c1设备树创建驱动编写Makefilegp7101_bl.c驱动触摸I2C驱动框架。驱动中的结构体probe函数devm_backlight_…

过滤器Filter和拦截器Interceptor实现登录校验

一.过滤器 Filter过滤器可以把对资源的请求拦截下来&#xff0c;从而实现一些登录验证的功能 1.Filter的快速入门 1.定义Filter:定义一个类&#xff0c;实现Filter接口&#xff0c;并重写其所有方法。2.配置 public class dofilter implements Filter {Override //初始化只…

【JAVA】数组的定义与使用

前一篇我们讲述了方法的使用和递归&#xff0c;这一讲 我们来叙述一下数组相关知识点。最近更新较快&#xff0c;大家紧跟步伐哦~~ 1. 数组的基本概念 1.1 为什么要使用数组 假设现在要存5个学生的javaSE考试成绩&#xff0c;并对其进行输出&#xff0c;按照之前掌握的知识点&…

obsidian 外观设置解毒

前言 一入obsidian深似海&#xff0c;外观设置也是五花八门&#xff0c;仿佛回到读书时期折腾桌面一样。 我对比了AnuPpuccin、minimal和其他的一些外观主题&#xff0c;设置都太复杂了&#xff0c;尤其是需要调整CSS文件&#xff0c;最后发现一款&#xff0c;非常好用&#…

数据传送指令

数据传送&#xff1a;负责把数据、地址或立即数传送到寄存器或存储单元中 数据传送指令可以分为四个类型&#xff1a;通用数据传送指令、地址传送指令、标志位传送指令、专用累加器传送指令(输入、输出指令) 一、通用数据传送指令 &#xff08;一&#xff09;传送字或字节指…

前端框架-echarts

Echarts 项目中要使用到echarts框架&#xff0c;从零开始在csdn上记笔记。 这是一个基础的代码&#xff0c;小白入门看一下 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" co…

刷题之找到字符串所有字母异位词

找到字符串所有字母异位词 滑动窗口。滑动窗口大小为待比较数组的大小。 class Solution { public:vector<int> findAnagrams(string s, string p) {//滑动窗口vector<int>result;if(s.size()<p.size())return result;vector<int>pnum(26,0);//记录p的字…