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

news2024/11/18 9:23:13

        

        在学习了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,则年份增加,并将月份重置为1
            if (_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; 

    // 返回自增操作前的日期对象tmp
    return 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; 

    // 返回自减操作执行前的日期对象tmp
    return tmp;
}

日期类的大小关系比较

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

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

>运算符的重载

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

// 重载大于比较运算符 '>', 用于比较两个Date对象的大小
bool Date::operator>(const Date& d) const
{
    // 首先比较年份,如果当前对象的年份大于参数对象的年份,则当前对象更大,返回true
    if (_year > d._year)
    {
        return true;
    }
    // 如果年份相同,则继续比较月份
    else if (_year == d._year)
    {
        // 当前对象的月份大于参数对象的月份,则当前对象更大,返回true
        if (_month > d._month)
        {
            return true;
        }
        // 如果月份也相同,则继续比较日
        else if (_month == d._month)
        {
            // 当前对象的日大于参数对象的日,则当前对象更大,返回true
            if (_day > d._day)
            {
                return true;
            }
        }
    }
    // 如果以上条件都不满足,说明当前对象不大于参数对象,返回false
    return 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
{
    // 使用逻辑或运算符检查当前对象是否大于(使用已重载的>运算符)或等于(使用已重载的==运算符)参数对象d
    return (*this > d) || (*this == d);
    // 如果任何一个条件为真(即当前对象大于d,或者等于d),则返回true,表示当前对象大于等于d
    // 否则,返回false
}

<运算符的重载

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

<=运算符的重载

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

!=运算符的重载

// 重载不等于比较运算符 '!=', 判断当前Date对象是否与另一个Date对象表示不同的日期
bool Date::operator!=(const Date& d) const
{
    // 通过逻辑非操作符'!'来反转等于运算的结果,从而判断当前对象是否不等于参数对象d
    return !(*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.coloradmin.cn/o/1675944.html

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

相关文章

嵌入式学习-M4的基本定时器

基本介绍 框图分析 时钟选择 计数器结构 开启重装载值寄存器的影子寄存器的工作时序图 未开启重装载值寄存器的影子寄存器的工作时序图 更新事件以及中断 相关寄存器 相关库函数

原子学习笔记7——FrameBuffer 应用编程

Frame 是帧的意思&#xff0c;buffer 是缓冲的意思&#xff0c;所以 Framebuffer 就是帧缓冲&#xff0c;这意味着 Framebuffer 就是一块内存&#xff0c;里面保存着一帧图像。 应用程序通过对 LCD 设备节点/dev/fb0&#xff08;假设 LCD 对应的设备节点是/dev/fb0&#xff09;…

Optional用法

说明&#xff1a;Optional和Stream一样&#xff0c;是Java8引入的特性&#xff0c;本文介绍Optional的几个实际用法。Steam流使用&#xff0c;参考下面这篇文章&#xff1a; Stream流使用 使用 1.保证值存在 // 1.保证值存在&#xff0c;pageNumber&#xff0c;pageSizeInte…

Zab之光:照亮分布式系统数据一致性迷宫的智慧火把

关注微信公众号 “程序员小胖” 每日技术干货&#xff0c;第一时间送达&#xff01; 引言 在构建大型分布式系统时&#xff0c;数据一致性是我们必须面对的挑战之一。随着业务的增长和系统规模的扩大&#xff0c;如何保证在多个节点间复制的数据保持一致&#xff0c;成为了一…

iOS--底层学习--GCD的简单认识

iOS--底层学习--GCD的简单认识 前言什么是GCDGCD的优点GCD中的任务和队列任务队列 GCD的使用队列的创建和获取任务的创建队列嵌套任务和队列中的一些要点 GCD线程间的通信从后台线程切换到主线程通过队列传递数据使用Dispatch Group进行线程间协调 GCD的方法dispatch_barrier_a…

其它高阶数据结构①_并查集(概念+代码+两道OJ)

目录 1. 并查集的概念 2. 并查集的实现 3. 并查集的应用 3.1 力扣LCR 116. 省份数量 解析代码1 解析代码2 3.2 力扣990. 等式方程的可满足性 解析代码 本篇完。 写在前面&#xff1a; 此高阶数据结构系列&#xff0c;虽然放在⑤数据结构与算法专栏&#xff0c;但还是作…

Output directory is not specified

场景&#xff1a;从GitHub拉取Java项目使用IDEA打开运行的时候抛出 java: 写入com.common.exception.ChatException时出错: Output directory is not specified网上大部分是说在项目结构增加编译器输出路径&#xff0c;但我在实际开发的项目的时候这里为空&#xff0c;包括我加…

IDEA找不到database图标的解决方法

首先右边侧边栏和左边的侧边栏都看一下&#xff0c;确认没有数据库图标以后再参考下面方法。 第一步&#xff0c;打开设置&#xff0c;在插件里搜索database 第二步 安装好&#xff0c;点击确定 返回主页面&#xff0c;左边的侧边栏会出现database图标&#xff0c;点击号就可以…

19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)

基础知识要求&#xff1a; Java&#xff1a;方法、while循环、for循环 Python&#xff1a; 方法、while循环、for循环 题目&#xff1a; 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head […

【C/C++笔试练习】DNS劫持、三次握手、TCP协议、HTTPS、四次挥手、HTTP报文、拥塞窗口、POP3协议、UDP协议、收件人列表、养兔子

文章目录 C/C笔试练习选择部分&#xff08;1&#xff09;DNS劫持&#xff08;2&#xff09;三次握手&#xff08;3&#xff09;TCP协议&#xff08;4&#xff09;HTTPS&#xff08;5&#xff09;四次挥手&#xff08;6&#xff09;HTTP报文&#xff08;7&#xff09;拥塞窗口&a…

宿舍管理系统代码详解(主页面)

本篇将对管理系统的主页面的代码进行详细的介绍。 目录 一、主页面前端代码 1.样式展示 2.代码详解 &#xff08;1&#xff09;template部分 &#xff08;2&#xff09;script部分 &#xff08;3&#xff09;路由导航守卫 &#xff08;4&#xff09;在vue中引用vue 一、主页…

富唯智能复合机器人:CNC铝块上下料安全新标准

在CNC铝块加工过程中&#xff0c;上下料环节的安全问题一直是企业关注的焦点。富唯智能复合机器人的应用&#xff0c;为这一环节树立了新的安全标准。 传统的上下料方式往往依赖于人工操作&#xff0c;存在着较大的安全隐患。而富唯智能复合机器人采用先进的视觉识别技术和精准…

Charger之二输入电压动态电源原理(VIN-DPM)

主要内容 Charger的VIN-DPM 前篇内容&#xff1a;电池管理IC&#xff08;Charger&#xff09;了解一下&#xff1f; 领资料&#xff1a;点下方↓名片关注回复&#xff1a;粉丝群 正文 一、 VIN-DPM概念 VIN-DPM是指输入电压动态电源管理&#xff08;Input voltage dynamic…

全栈开发之路——前端篇(9)插槽、常用api和全局api

全栈开发一条龙——前端篇 第一篇&#xff1a;框架确定、ide设置与项目创建 第二篇&#xff1a;介绍项目文件意义、组件结构与导入以及setup的引入。 第三篇&#xff1a;setup语法&#xff0c;设置响应式数据。 第四篇&#xff1a;数据绑定、计算属性和watch监视 第五篇 : 组件…

OpenAI 把超强AI带进日常,GPT-4o 让机器也懂情感!

一、前言 ⭐⭐ 立即体验&#xff1a;GPT-4o OpenAI 在春季发布会上推出了名为 GPT-4o 的旗舰级生成式人工智能模型&#xff0c;这一模型的发布不仅标志着技术的巨大飞跃&#xff0c;更预示着人机交互方式的全面革新。"o" 在 GPT-4o 中代表 "omni"&#xf…

Codeforces Round 944 (Div. 4)(A,B,C,D,E,F,G,H)

比赛链接 这场不难&#xff0c; G G G 和 H H H 比较有意思。 G G G 题需要一定的二进制和数据结构的知识&#xff0c; H H H 题是个 2 − s a t 2-sat 2−sat 的题&#xff0c;算法名字吓人但是其实很简单&#xff0c;题目本身也很板&#xff0c;建议趁机学习一波。 A. My …

vue获取路由的值

1&#xff0c;此方法获取到请求地址后面的值 如 /name123&age12 2&#xff0c;此方法获取到请地址&#xff1f;后面的值 例如?name123&age12 二者的区别&#xff0c;第一个是直接在路径后面拼接&#xff0c;第二种就是正规的http请求。 路径带&#xff1f;号的

Ajax 学习

文章目录 1. 前置知识1.1 ajax 介绍1.2 XML 简介 2. AJAX 学习2.1 AJAX基础学习&#xff08;1&#xff09;AJAX的特点&#xff08;2&#xff09;AJAX 初体验&#xff08;3&#xff09;服务端响应json 数据 2.2 IE 缓存问题2.3 请求超时和网络异常2.4 手动取消请求2.5 重复请求2…

使用DBeaver的第2天-使用sql导入数据

使用sql导入数据这块我会仔细的说一下 首先位置一定要放在库上&#xff08;实例&#xff09;&#xff0c;放在表上可不好使用哦 然后点击工具-再点击执行脚本 这样就执行成功了 但是如果你执行失败了&#xff0c;多半可能是因为本地没有部署mysql&#xff0c;记住只有本地有…

搜索引擎的设计与实现(四)

目录 6 系统测试 6.1测试重要性 6.2测试用例 结 论 参 考 文 献 前面内容请移步 搜索引擎的设计与实现&#xff08;三&#xff09; 免费源代码&毕业设计论文 搜索引擎的设计与实现 6 系统测试 6.1测试重要性 该项目是在本地服务器上进行运行和调试&#xff0c;…