当前位置: 首页 > web >正文

【C++】日期类实现详解:代码解析与复用优化

代码总览与总结

// 实现一个完善的日期类
class Date
{
public:int GetMonthDay(int year, int month){int monthDays[13] = { 0,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;return monthDays[month];}Date(int year = 0, int month = 1, int day = 1){// 如果日期合法if (year >= 0 && month >= 1 && month <= 12 && day >= 1 && day <= GetMonthDay(year,month)){_year = year;_month = month;_day = day;}else{cout << "非法日期" << endl;}}Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}bool operator==(const Date& d){return _year == d._year&& _month == d._month&& _day == d._day;}bool operator<(const Date& d){if (_year < d._year)return true;else if (_year == d._year && _month < d._month)return true;else if (_year == d._year && _month == d._month && _day < d._day)return true;return false;}bool operator<=(const Date& d){// 函数原型为 operator<=(Date* this, const Date& d)// 函数调用时相当于传参 operator<=(&d1, d2)// 此处 *this = *(&d1),即d1return *this < d || *this == d;// 所以上面的代码等价于// return d1 < d2 || d1 == d2;}// 通过 *this ,我们可以很轻松实现代码的复用// 代码的复用有很多好处,当未来加入什么“时分秒”时不需要修改全部函数// 只需要修改一开始的少数函数就可以达成修改全部函数的目的// 复用是高内聚,写代码的提倡的就是低耦合高内聚bool operator>(const Date& d){return !(*this <= d);}bool operator>=(const Date& d){return !(*this < d);}bool operator!=(const Date& d){return !(*this == d);}// +//Date operator+(int day)//{//        Date ret(*this);        // 拷贝构造一个ret//        ret._day += day;//        while (ret._day > GetMonthDay(ret._year, ret._month))//        {//                // 日期大于天数上限,往月进位//                ret._day -= GetMonthDay(ret._year, ret._month);//                ret._month++;//                // 检查月份合法性//                if (ret._month > 12)//                {//                        ret._month = 1;//                        ret._year++;//                }//        }//        return ret;//}// +// 复用实现Date operator+(int day){Date ret(*this);        // 拷贝构造一个retret += day;                        // ret.operator+=(day)return ret;}// +=// 类对象本身在函数外,所以可以传引用返回Date& operator+=(int day){if (day < 0){return *this -= -day;}_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;if (_month > 12){_year++;_month = 1;}}return *this;}// -/*Date operator-(int day){Date ret(*this);ret._day -= day;while (ret._day <= 0){ret._month--;if (ret._month <= 0){ret._year--;ret._month = 12;}ret._day += GetMonthDay(ret._year, ret._month);}return ret;}*/// -// 复用实现Date operator-(int day){Date ret(*this);ret -= day;                        // operator-=(day)return ret;}// -=Date& operator-=(int day){if (day < 0){return *this += -day;}_day -= day;while (_day <= 0){_month--;if (_month <= 0){_year--;_month = 12;}_day += GetMonthDay(_year, _month);}return *this;}// ++(前置++)Date& operator++(){*this += 1;return *this;}// ++(后置++)// 为了构成函数重载,函数参数加了一个int,但实际不用传参数// 这个int仅仅是编译器为了构成函数重载加上的Date operator++(int){Date tmp(*this);*this += 1;return tmp;}// --(前置--)Date& operator--(){*this -= 1;return *this;}//--(后置--)// 为了构成函数重载,函数参数加了一个int,但实际不用传参数// 这个int仅仅是编译器为了构成函数重载加上的Date operator--(int){Date tmp(*this);*this -= 1;return tmp;}// d1 - d2int operator-(const Date& d){int flag = 1;Date max = *this;        // 拷贝构造Date min = d;if (*this < d){max = d;                // operator=min = *this;flag = -1;}int n = 0;while (min != max){++min;        // 参考前置++与后置++的实现,自定义类型能用前置++就不要用后置++n;}return n * flag;}// 赋值运算符重载// d3 = d1// 运算符的重载是为了让自定义类性可以像内置类型一样去使用运算符Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};

1. 核心功能

  • 日期计算:加减天数、递增递减(前置/后置)

  • 日期比较:全部6种关系运算符(==, !=, <, <=, >, >=)

  • 日期差值:计算两个日期之间相差的天数

    2. 设计亮点

    • 高内聚低耦合:通过运算符复用来减少代码冗余(例如,用 ==< 实现其他所有比较运算符;用 += 实现 +)。

    • 鲁棒性:构造函数进行了日期合法性检查;加减操作处理了负数和跨年月的情况。

    • 效率考虑:在日期差值计算中,使用小日期自增到大日期的方式,简单可靠。

      3. 关键技术点

      • 闰年的判断与二月份天数的动态获取。

      • 前置与后置自增/自减运算符的重载区分。

      • 赋值运算符的自我赋值检查。

      • 对于 +=-=,处理了传入负数的特殊情况,增强了接口的健壮性。


        分段代码讲解

        1. 辅助函数与构造函数

        int GetMonthDay(int year, int month)
        {int monthDays[13] = { 0,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;return monthDays[month];
        }Date(int year = 0, int month = 1, int day = 1)
        {// 检查日期合法性if (year >= 0 && month >= 1 && month <= 12 && day >= 1 && day <= GetMonthDay(year,month)){_year = year;_month = month;_day = day;}else{cout << "非法日期" << endl;// 注意:这里最好抛出一个异常,或者将日期设为一个确定的合法状态(如1970-1-1),而不是仅仅打印。}
        }
        • GetMonthDay: 根据年份和月份返回对应的天数,核心是处理闰年二月的特殊情况。

        • 构造函数: 提供了全缺省参数。首先检查传入的日期是否合法,合法则初始化,否则输出错误信息。**建议改进**:在非法日期时进行错误处理(如抛出异常),而不仅仅是打印。

          2. 拷贝构造与基本比较运算符

          Date(const Date& d)
          {_year = d._year;_month = d._month;_day = d._day;
          }bool operator==(const Date& d)
          {return _year == d._year&& _month == d._month&& _day == d._day;
          }bool operator<(const Date& d)
          {if (_year < d._year)return true;else if (_year == d._year && _month < d._month)return true;else if (_year == d._year && _month == d._month && _day < d._day)return true;return false;
          }
          • 拷贝构造函数: 进行深拷贝(虽然这里都是int,但习惯上称为深拷贝)。

          • operator==: 严格比较年、月、日是否全部相等。

          • operator<: 实现了日期的字典序比较,是其他所有比较运算符的基石。

            3. 复用实现的比较运算符

            bool operator<=(const Date& d)
            {return *this < d || *this == d;
            }bool operator>(const Date& d)
            {return !(*this <= d);
            }bool operator>=(const Date& d)
            {return !(*this < d);
            }bool operator!=(const Date& d)
            {return !(*this == d);
            }
            • 复用思想:这是代码最大的亮点。只需要实现 ==<,其余四个运算符都可以通过逻辑组合轻松实现。这使得代码维护性极高,如需修改比较逻辑,只需改动 ==< 即可。

              4. 复合赋值运算符(+=, -=)

              Date& operator+=(int day)
              {if (day < 0) // 处理负数情况:加负天数等于减正天数{return *this -= -day;}_day += day;while (_day > GetMonthDay(_year, _month)) // 当天数溢出时循环调整{_day -= GetMonthDay(_year, _month);_month++;if (_month > 12) // 月份溢出,进年{_year++;_month = 1;}}return *this; // 返回自身的引用,以支持连续赋值(如 d1 += 5 += 10;)
              }Date& operator-=(int day)
              {if (day < 0) // 处理负数情况:减负天数等于加正天数{return *this += -day;}_day -= day;while (_day <= 0) // 当天数变为0或负数时,需要借位{_month--;if (_month <= 0) // 月份借完,向年借{_year--;_month = 12;}_day += GetMonthDay(_year, _month); // 借来一个月的天数加到当前天数上}return *this;
              }
              • 核心算法:这两个函数是日期计算的核心。+= 通过循环进位,-= 通过循环借位,正确处理了跨月、跨年的复杂情况。

              • 接口健壮性:处理了传入负数的边界情况,使接口更友好。

              • 返回值:返回引用 (Date&),支持链式操作(如 d1 += 10 -= 5;),这是符合内置类型行为的标准做法。

                5. 算术运算符(+, -)与递增递减(++, --)

                // 复用 += 实现 +
                Date operator+(int day)
                {Date ret(*this); // 创建当前对象的一个副本ret += day;      // 对副本进行操作return ret;      // 返回副本的结果
                }// 复用 -= 实现 -
                Date operator-(int day)
                {Date ret(*this);ret -= day;return ret;
                }// 前置++:返回自增后的自身
                Date& operator++()
                {*this += 1;return *this;
                }// 后置++:返回自增前的值(副本)
                Date operator++(int)
                {Date tmp(*this); // 保存原来的值*this += 1;      // 自身增加return tmp;      // 返回原来的值
                }// 前置--和后置--的实现逻辑与++完全相同
                Date& operator--()
                {*this -= 1;return *this;
                }Date operator--(int)
                {Date tmp(*this);*this -= 1;return tmp;
                }
                • 复用思想+- 运算符通过复用 +=-= 实现,避免了重复的进位/借位逻辑。

                • 前后置区分

                  • 前置:先计算,后返回自身的引用 (Date&)。

                  • 后置:使用 int 参数作为占位符以区分重载。先保存原状态,再计算,最后返回原状态的副本 (Date)。

                  6. 日期差值运算符(-)

                  int operator-(const Date& d)
                  {int flag = 1;Date max = *this;Date min = d;if (*this < d) // 确定大小关系,保证max是较大的日期{max = d;min = *this;flag = -1; // 如果this < d,结果应为负数}int n = 0;while (min != max) // 让小日期不断自增,直到等于大日期{++min; // 使用前置++,效率更高++n;}return n * flag; // 返回带符号的天数差
                  }
                  • 算法:采用朴素的“计数”方法,让小日期一天天加到大日期,计数器的值就是差值。方法简单正确,但对于相差很远的日子效率较低(可采用计算绝对日期数再相减的优化算法)。

                  • 符号处理:通过 flag 变量控制结果的符号,使 d1 - d2 的结果与预期一致。

                    7. 赋值运算符与打印函数

                    Date& operator=(const Date& d)
                    {if (this != &d) // 防止自我赋值(如 d1 = d1;){_year = d._year;_month = d._month;_day = d._day;}return *this; // 返回自身引用,支持链式赋值(d1 = d2 = d3;)
                    }void Print()
                    {cout << _year << "-" << _month << "-" << _day << endl;
                    }
                    • 赋值运算符:是拷贝构造的伴侣。注意**自我赋值检查** (if (this != &d)),这是一个重要的安全措施。

                    • Print函数:一个简单的格式化输出功能。

                      总结

                      这个 Date 类清晰地展示了:

                      1. 如何为自定义类型重载运算符,使其行为与内置类型一致。

                      2. 代码复用的强大威力,极大地减少了代码量和维护成本。

                      3. 边界条件处理的重要性(如闰年、日期合法性、负数操作、自我赋值等)。

                        可以进行的改进点

                        • 构造函数在遇到非法日期时,可以抛出异常 (std::invalid_argument) 而不是仅输出信息。

                        • operator- (计算差值) 的算法可以优化为计算每个日期相对于某个固定日期(如0000-01-01)的天数,然后相减,以提升效率。

                        • 可以添加 const 成员函数,如 void Print() constbool operator<(const Date& d) const,表示这些函数不会修改对象状态,可以在常量对象上调用。

                        http://www.xdnf.cn/news/19367.html

                        相关文章:

                      4. C#正则表达式与用法
                      5. 【基础-单选】关于Tabs组件页签的位置设置,下面描述错误的是
                      6. 免费在线图片合成视频工具 ,完全免费
                      7. uni.onBLECharacteristicValueChange接收到数据,返回的value为{}的原因及其获取方法
                      8. 佳易王钟表维修养护管理系统:开启钟表维修高效管理新篇章​就#软件操作教程
                      9. Mysql 学习day 2 深入理解Mysql索引底层数据结构
                      10. React前端开发_Day6-Day9_极客园项目
                      11. C语言 - 输出参数详解:从简单示例到 alloc_chrdev_region
                      12. Spring AI 的应用和开发
                      13. 如何简单建设一个网站,让用户快速找到你。
                      14. 在PowerPoint和WPS演示让蝴蝶一直跳8字舞
                      15. Python生成免安装exe
                      16. SAP PP模块的MPS
                      17. Vue加载速度优化,verder.js和element.js加载速度慢解决方法
                      18. 防火墙技术(二):安全区域
                      19. C#调用c++ dll读取2进制文件时而正常,时而异常
                      20. 语义分割目前还是研究热点吗?
                      21. 如何快速了解项目管理基础
                      22. 【具身智能】【机械臂】机械臂轨迹规划项目以及资料汇总【持续更新】
                      23. 【物联网】MQTT / Broker / Topic 是什么?
                      24. windows 谷歌浏览器把英文改成中文
                      25. 【路由器】TP Link 路由器为何无法进入管理后台
                      26. 关于铭飞平台企业官网模板使用中常到的问题、企业官网的百度认证以及IDEA编辑启动器的快捷方法/Apipost本地和云端没法同步的问题解决
                      27. 【软考架构】SOA与微服务解疑
                      28. React Hooks深度解析与最佳实践:提升函数组件能力的终极指南
                      29. Unity笔记(八)——资源动态加载、场景异步加载
                      30. 迷你电脑用到什么型号的RJ45网口
                      31. 揭秘表格推理的“思维革命”:RoT模型介绍
                      32. seafile-setup-troubleshooting_# Seafile 安装与问题解决记录 # Seafile/Seahub 启动问题记录文档
                      33. linux基础——UDP、TCP