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.hqwc.cn/news/701177.html

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

相关文章

VictoriaMetrics

概念 介绍 VictoriaMetrics&#xff0c;是一个快速高效、经济并且可扩展的监控解决方案和时序数据库 本文均用VM简称VictoriaMetric 作用 用于作为prometheus的长期储存方案&#xff0c;代替prometheus存储监控采集的数据 优点 远程存储&#xff1a;可作为单一或多个Pro…

Python中tkinter编程入门4

在Python中tkinter编程入门3-CSDN博客中创建了Button控件&#xff0c;点击该控件就会产生一个点击事件&#xff0c;在创建Button控件时指定该点击事件的处理程序后&#xff0c;按键控件就会对用户的点击事件产生响应。 1 定义事件处理器 定义事件处理器就是一个自定义的函数。…

matlab使用1-基础

matlab使用1-基础 文章目录 matlab使用1-基础1. 界面介绍2. matlab变量3. matlab数据类型4. matlab矩阵操作5. matlab程序结构5.1 顺序结构5.2 循环结构5.3 分支结构 1. 界面介绍 命令行窗口输入&#xff1a;clc 可清除命令行窗口command window的内容 clc命令行窗口输入&…

【知识拓展】大白话说清楚:IP地址、子网掩码、网关、DNS等

前言 工作中常听别人说的本地网络是什么意思&#xff1f;同一网段又是什么意思&#xff1f;它俩有关系吗&#xff1f; 在工作中内经常会遇到相关的网络问题&#xff0c;涉及网络通信中一些常见的词汇&#xff0c;如IP地址、子网掩码、网关和DNS等。具体一点&#xff1a;经常会…

添砖Java之路(其八)——继承,final关键字

目录 继承&#xff1a; super关键字&#xff1a; 方法重写&#xff1a; 继承特点&#xff1a; 继承构造方法&#xff1a; final关键字&#xff1a; 继承&#xff1a; 意义&#xff1a;让类于类之间产生父类于子类的关系&#xff0c;子类可以直接使用父类中的非私有成员(包…

基准电流源电路仿真

1.补全电路 2.更改vpluse属性 3.添加tran仿真&#xff0c;因为加入启动电路主要看的就是tran仿真 4.启动仿真 5.看电路曲线 先点这个main form 不用选择ok&#xff0c;直接点中四条线&#xff0c;中间第2条曲线如果出现那样一个小波动后面没有起伏就说明成功了。

国产分布式数据库高可用故障检测实现

在分布式数据库架构下&#xff0c;当数据库节点异常时&#xff0c;数据库管理组件能够自动感知到异常并触发节点隔离或者自动切换&#xff0c;是数据库高可用容灾的基本能力。在节点服务器异常、网络异常或进程异常等场景下&#xff0c;各数据库产品本身已经具备了可靠的检测能…

jmeter中java请求,解决不支持协议和元件,实现自定义元件

目录 java请求 作用场景 JavaTest类源码分析 编写java请求样例 新建java工程&#xff0c;导入jmeter主要依赖。 编写java请求类&#xff0c;继承AbstractJavaSamplerClient, 导入工程为jar包&#xff0c;放置jmeter安装目录下lib/ext目录 重启jmeter&#xff0c;添加ja…

03 Linux编程-进程

1、进程的相关概念 1.1 程序与进程 程序是静态的概念&#xff0c;进程是程序的一次运行活动。 1.2 查看系统中有哪些进程 ps #只显示一小部分进程 ps -aux #会打印当前所有进程 ps -aux|grep init #使用grep筛选出只含有init的进程top #运行显示的进程有点类似windows…

服务攻防——应用协议ssh,rsync,proftpd,openssh,libssh

1.口令猜解 ftp-拿来文件传输的 rdp-windows远程连接 3389 ssh-linux远程连接 工具hydra 口令 1.windows 这就爆破成功了&#xff0c;现在&#xff0c;我们就可以ftp爆破&#xff0c;爆破出ftp的密码 爆破出来后 访问 2.ssh Rsync&#xff08;配置不当&#xff0c;未授权…

云服务器和主机的区别

在今天的数字化时代&#xff0c;对于个人和企业来说&#xff0c;选择适当的服务器托管解决方案至关重要。然而&#xff0c;很多人对于云服务器和传统主机之间的区别不太清楚。本文将为您提供一个详细的指南&#xff0c;帮助您理解云服务器与主机之间的区别&#xff0c;以便您能…

【vue+el-upload】当action=“#“,代表不使用默认上传,使用自定义上传,http-request获取文件流

el-upload有多种上传行为&#xff1a; 1、立即上传&#xff1a; 当 action 属性被赋予一个有效的 URL 时&#xff0c;一旦用户选择了文件&#xff0c;el-upload 组件会立即自动将文件上传到指定的服务器地址。 2、不立即上传&#xff08;自定义触发&#xff09;&#xff1a; 如…