• 【C++深入浅出】日期类的实现



    目录

    一. 前言 

    二. 日期类的框架

    三. 日期类的实现

    3.1 构造函数

    3.2 析构函数

    3.3 赋值运算符重载

    3.4 关系运算符重载

    3.5 日期 +/- 天数

    3.6 自增与自减运算符重载

    3.7 日期 - 日期

    四. 完整代码 


    一. 前言 

            通过前面两期类和对象的学习,我们已经对C++的类有了一定的了解。本期我们的目标是实现一个完整的日期类,通过实现日期类的构造函数、运算符重载等等内容,加深对前面知识的理解。

            实现了日期类之后,我们就相当于自己实现了一个网上的日期计算器,如下所示

             上面的两项功能,都是通过我们接下来要进行的运算符重载实现的。

    二. 日期类的框架

            以下是我们本期实现的日期类的基本框架

    1. class Date
    2. {
    3. public:
    4. // 全缺省的构造函数
    5. Date(int year = 2023, int month = 1, int day = 1);
    6. // 拷贝构造函数
    7. Date(const Date& d);
    8. // 赋值运算符重载
    9. Date& operator=(const Date& d);
    10. // 析构函数
    11. ~Date() {};
    12. // +、=等算数运算符重载
    13. Date& operator+=(int day); //日期+=天数
    14. Date operator+(int day); //日期+天数
    15. Date& operator-=(int day); //日期-=天数
    16. Date operator-(int day); //日期-天数
    17. // 日期-日期 返回天数,即日期差
    18. int operator-(const Date& d);
    19. // 自增、自减运算符重载
    20. Date& operator++(); //前置++
    21. Date operator++(int); //后置++
    22. Date operator--(int); //后置--
    23. Date& operator--(); //前置--
    24. // >、<、==等关系运算符重载
    25. bool operator>(const Date& d);
    26. bool operator==(const Date& d);
    27. bool operator >= (const Date& d);
    28. bool operator < (const Date& d);
    29. bool operator <= (const Date& d);
    30. bool operator != (const Date& d);
    31. //打印数据
    32. void Print();
    33. private:
    34. int _year; //年
    35. int _month; //月
    36. int _day; //日
    37. };

    三. 日期类的实现

    3.1 构造函数

            构造函数的目的是初始化类对象,这里我们只需要实现两种构造函数即可。一种是全缺省的构造函数,使用全缺省的构造函数可以让类对象构造时传参更加灵活;而另一种则是拷贝构造函数

            全缺省的构造函数

            很简单,我们只需要判断传入的参数是否符合生活中日期的规则,如果符合则正常进行初始化,反之则程序退出。规则大致如下

    日期规则

    1、月份必须在1月到12月之间。例如0月和13月是非法的

    2、日份必须在当前月的天数范围内。例如1月32日,4月31日是非法的

            由于每个月的天数不同,尤其是2月,还有闰年平年之分,故我们可以先写个函数用来获取当前月的天数。这个函数不仅可以用在日份的判断,在之后也会经常用到。

    1. // 获取某年某月的天数
    2. int GetMonthDay(int year, int month)
    3. {
    4. //用一个数组表示平年每月的天数,下标表示月份
    5. int MouthDayArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
    6. if (month == 2 &&(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) //判断闰年
    7. {
    8. MouthDayArray[2]++; //是闰年,2月份天数+1
    9. }
    10. return MouthDayArray[month]; //返回当前月天数
    11. }

            有了上面的函数, 再根据之前的规则,我们很容易就可以写出构造函数

    1. // 全缺省的构造函数
    2. Date(int year = 2023, int month = 1, int day = 1)
    3. {
    4. if ( month < 1 || month > 12 || day < 1 || day > GetMonthDay(year, month))
    5. {
    6. cout << "日期非法" << endl;
    7. exit(-1);
    8. }
    9. _year = year;
    10. _month = month;
    11. _day = day;
    12. }

            拷贝构造函数

            拷贝构造函数就更简单了,拷贝构造函数是用已存在的对象初始化新对象,故不需要考虑日期是否合法,直接拷贝即可,如下:

    1. // 拷贝构造函数, d2(d1)
    2. Date(const Date& d) //加const避免d被意外修改,使用引用传递是为了提高效率
    3. {
    4. _year = d._year;
    5. _month = d._month;
    6. _day = d._day;
    7. }

    3.2 析构函数

            由于日期类不涉及动态内存管理,因此使用编译器自动生成的析构函数即可,不需要显式进行实现。 

    3.3 赋值运算符重载

            同理,由于日期类不涉及动态内存管理,因此只要进行普通的浅拷贝即可,编译器默认生成的赋值运算符重载函数即可完成要求。

    1. //赋值运算符重载,不需要显式进行实现,这里使用编译器默认生成的即可
    2. Date& operator=(const Date& d)
    3. {
    4. _year = d._year;
    5. _month = d._month;
    6. _day = d._day;
    7. return *this; //返回当前对象的引用为了实现链式访问
    8. }

    3.4 关系运算符重载

            关系运算符有><>=<===以及!=,我们先来实现">"运算符。

            我们要如何判断一个日期大于另一个日期呢?很简单,先比较年,年相等则比较月,月相等则比较日,直到比出结果。代码如下:

    1. // >运算符重载
    2. bool operator>(const Date& d)
    3. {
    4. if (_year > d._year) //比较年
    5. {
    6. return true;
    7. }
    8. else if (_year == d._year && _month > d._month) //年相等比较月
    9. {
    10. return true;
    11. }
    12. else if (_year == d._year && _month == d._month && _day > d._day) //年月都相等比较日
    13. {
    14. return true;
    15. }
    16. else //前面的条件都不符合,说明日期比较小,返回false
    17. {
    18. return false;
    19. }
    20. }

            接下来是"=="运算符,这就简单啦,看看年月日是否都相等即可 

    1. // ==运算符重载
    2. bool operator==(const Date& d)
    3. {
    4. // 逻辑与,下面的条件都符合才返回true,否则为false
    5. return (_year == d._year) && (_month == d._month) && (_day == d._day);
    6. }

            然后是"<"运算符,我知道我知道,把上面">"运算符的逻辑都反过来再敲一遍就好啦 

            这种方法固然可以,但未免会显得过于冗余。实际上,我们已经实现了">"运算符和"=="运算符重载,那"<"不就是既不大于也不等于嘛,我们复用一下之前的重载函数即可,如下

    1. // <运算符重载
    2. bool operator<(const Date& d)
    3. {
    4. //*this表示当前对象
    5. return !((*this == d) || (*this > d)); //逻辑非之后表示既不大于也不等于
    6. }

            通过复用的方法,剩下的"<="、">="和"!="我们也可以很轻松的写出来,如下:

    1. // >=运算符重载
    2. bool operator>=(const Date& d)
    3. {
    4. return !(*this < d); //复用<运算符
    5. }
    6. // <=运算符重载
    7. bool operator<=(const Date& d)
    8. {
    9. return !(*this > d); //复用>运算符
    10. }
    11. // !=运算符重载
    12. bool operator!=(const Date& d)
    13. {
    14. return !(*this == d); //复用==运算符
    15. }

    小贴士:实现类中成员函数时,并不意味着每个都要将其逻辑完整地写出,有些函数之间可以相互复用,简化代码。例如上面我们只需实现">"号和"=="号的重载函数,其余的函数直接复用即可。

    3.5 日期 +/- 天数

            接下来我们来实现日期计算器的第一个功能:推算几天后的日期。通过重载"+/-"号运算符我们可以实现日期 +/- 指定天数

             日期 + 指定天数

             首先,我们要知道,日期的进位规则和我们的十进制不一样。每个月的天数都不同,当超过本月的天数,月份都要进1,并且每过12个月年份都要进1。故我们需要用到之前实现的获取本月天数的函数,用来判断是否需要进位。

            例如,求在12月3号的基础上100天后是几月几号?计算过程如下

     

            下面给出示例代码(初版): 

    1. // 日期+=天数(初版)
    2. Date& operator+=(int day)
    3. {
    4. _day += day; //先将天数全部加到_day上
    5. //下面开始进行进位
    6. while (_day > GetMonthDay(_year, _month)) //当_day大于本月的天数说明还需要进行进位,继续
    7. {
    8. //月进位
    9. _day -= GetMonthDay(_year, _month); //减去当前月的天数
    10. ++_month; //来到下一月
    11. //月进位后判断年是否需要进位
    12. if (_month == 13)
    13. {
    14. ++_year;
    15. _month = 1; //进位后月份来到了1月
    16. }
    17. }
    18. return *this;
    19. }

    不过需要注意的是:上面的代码我们对日期进行了修改,返回的是修改后的对象,故实现的不是"+"号的运算符重载,而是"+="运算符的重载。

            那如果我们要实现"+"号呢?很简单,拷贝一份原对象,然后对拷贝后的对象复用"+="运算符重载即可,这样就不会对原对象进行修改。代码如下:

    1. // 日期+天数
    2. Date operator+(int day)
    3. {
    4. Date tmp(*this); //拷贝构造一个临时变量
    5. tmp += day; //复用+=运算符,此时tmp就是加上day后的日期,但原对象并没有改变
    6. return tmp; //以值的形式返回
    7. }

            日期 - 指定天数

            实现了推算几天后的日期,我们再来实现推算几天前的日期。同样的,我们先实现"-="运算符的重载,然后"-"号运算符进行复用即可。

            日期+天数是进行进位,那么日期-天数就是进行借位退位,二者互为逆过程。有一点需要注意的是,退位借的是上一个月的天数,故我们需要先让月先减再让天数增加。动图如下:

            下面是是示例代码(初版): 

    1. // 日期-=天数(初版)
    2. Date& operator-=(int day)
    3. {
    4. _day -= day; //先全部减到_day上
    5. while (_day <= 0) //_day小于0说明需要借位,进行循环
    6. {
    7. //月先借位
    8. --_month;
    9. //月借位后判断年是否需要借位
    10. if (_month == 0)
    11. {
    12. --_year; //年借位
    13. _month = 12; //月变为上一年12月
    14. }
    15. _day += GetMonthDay(_year, _month); //将借到的天数进行累加
    16. }
    17. return *this;
    18. }

             实现了日期-=天数,那么日期-天数直接复用即可

    1. // 日期-天数
    2. Date operator-(int day)
    3. {
    4. Date tmp(*this); //拷贝构造
    5. tmp -= day; //复用-=运算符
    6. return tmp;
    7. }

            细心的小伙伴可能发现了,上面我们写的"+=""-="运算符重载都是初版,是有哪里要改进吗?没错,其实上面的代码在特殊情况下有出错的风险,即用户传入的天数如果是个负数,那么最终得到的答案可能就不是我们想要的。但是我们再来看看网上的日期计算器

            没错,网上的日期计算器对负数有着特殊处理,输入负数时则是向前计算,即减去相应的天数。故我们可以参考进行实现:

    1. // 日期+=天数(终版)
    2. Date& operator+=(int day)
    3. {
    4. if (day < 0) //先判断day的正负
    5. {
    6. *this -= (-day); //为负数则复用-=运算符
    7. return *this; //直接返回
    8. }
    9. _day += day; //先将天数全部加到_day上
    10. //下面开始进行进位
    11. while (_day > GetMonthDay(_year, _month)) //当_day大于本月的天数说明还需要进行进位,继续
    12. {
    13. //月进位
    14. _day -= GetMonthDay(_year, _month); //减去当前月的天数
    15. ++_month; //来到下一月
    16. //月进位后判断年是否需要进位
    17. if (_month == 13)
    18. {
    19. ++_year;
    20. _month = 1; //进位后月份来到了1月
    21. }
    22. }
    23. return *this;
    24. }

    对于"+="运算,我们通过复用"-="实现对负数的特殊处理。而对于"-="运算,我们可以复用"+="实现对负数的特殊处理,这里就不再展开了。

    3.6 自增与自减运算符重载

            在上面实现日期+=天数和日期-=天数的基础上,我们通过复用可以很轻松地实现自增和自减运算符重载,代码如下:

    1. // 前置++
    2. Date& operator++()
    3. {
    4. *this += 1; //先+1
    5. return *this; //再返回,返回+1后的结果
    6. }
    7. // 后置++
    8. Date operator++(int) //int用于占位,表示后置++
    9. {
    10. Date tmp(*this); //拷贝一份
    11. *this += 1; //然后+1
    12. return tmp; //返回+1前的结果
    13. }
    14. // 前置--
    15. Date& operator--()
    16. {
    17. *this -= 1; //先-1
    18. return *this; //再返回,返回-1后的结果
    19. }
    20. // 后置--
    21. Date operator--(int) //int用于占位,表示后置--
    22. {
    23. Date tmp(*this); //拷贝一份
    24. *this -= 1; //然后-1
    25. return tmp; //返回-1前的结果
    26. }

    3.7 日期 - 日期

            我们知道,日期+日期是没有意义的,但日期-日期却是很有价值的,可以用来获取两个日期之间间隔的天数。故我们最后还要来实现"-"号运算符的另外一个重载版本,用于进行日期-日期的运算。

            由于日期类的运算规则较为复杂,涉及到每年的天数不同每月的天数不同,以及平闰年之分,如果采用正常的年-年,月-月的方法,想想都头大

            故我们另辟稀径,我们可以先找出较小的那个日期,然后让较小的日期不断+1,用一个变量count来统计+1的次数,直到较小的日期和较大的日期相等时,count的值不就是我们要求的差值嘛代码如下:

    1. // 日期-日期 返回天数
    2. //法1:年-年,月-月,日-日,太过复杂
    3. //法2:找出较小的那个,一直累加直到等于大的那个,累加的次数就是之间的天数
    4. int operator-(const Date& d)
    5. {
    6. //找出较小的那个日期,这里先假设*this较小
    7. Date min = *this;
    8. Date max = d;
    9. int flag = -1; //用于表示最后相减结果的符号
    10. if (min > max) //假设不成立,互换
    11. {
    12. min = d;
    13. max = *this;
    14. flag = 1;
    15. }
    16. //小的进行累加,直到二者相等
    17. int ret = 0; //统计min累加的次数
    18. while (max > min)
    19. {
    20. min++;
    21. ret++;
    22. }
    23. return ret*flag; //最终累加的次数即为日期差
    24. }

    四. 完整代码 

            最后给出本期日期类的完整代码,需要的读者自取

    1. class Date
    2. {
    3. public:
    4. // 获取某年某月的天数
    5. int GetMonthDay(int year, int month)
    6. {
    7. //用一个数组表示平年每月的天数,下标表示月份
    8. int MouthDayArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
    9. if (month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) //判断闰年
    10. {
    11. MouthDayArray[2]++; //是闰年,2月份天数+1
    12. }
    13. return MouthDayArray[month]; //返回当前月天数
    14. }
    15. // 全缺省的构造函数
    16. Date(int year = 2023, int month = 1, int day = 1)
    17. {
    18. if (month < 1 || month > 12 || day < 1 || day > GetMonthDay(year, month))
    19. {
    20. cout << "日期非法" << endl;
    21. exit(-1);
    22. }
    23. _year = year;
    24. _month = month;
    25. _day = day;
    26. }
    27. // 拷贝构造函数, d2(d1)
    28. Date(const Date& d) //加const避免d被意外修改,使用引用传递是为了提高效率
    29. {
    30. _year = d._year;
    31. _month = d._month;
    32. _day = d._day;
    33. }
    34. //赋值运算符重载,不需要显式进行实现,使用编译器默认生成的即可
    35. Date& operator=(const Date& d)
    36. {
    37. _year = d._year;
    38. _month = d._month;
    39. _day = d._day;
    40. return *this; //返回当前对象的引用为了实现链式访问
    41. }
    42. // 析构函数
    43. ~Date() {};
    44. // 日期+=天数
    45. Date& operator+=(int day)
    46. {
    47. if (day < 0)
    48. {
    49. *this -= (-day);
    50. return *this;
    51. }
    52. _day += day;
    53. while (_day > GetMonthDay(_year, _month))
    54. {
    55. //月进位
    56. _day -= GetMonthDay(_year, _month);
    57. ++_month;
    58. //年进位
    59. if (_month == 13)
    60. {
    61. ++_year;
    62. _month = 1;
    63. }
    64. }
    65. return *this;
    66. }
    67. // 日期+天数
    68. Date operator+(int day)
    69. {
    70. if (day < 0)
    71. {
    72. return *this - (-day) ;
    73. }
    74. Date tmp(*this); //拷贝构造
    75. tmp += day; //复用+=运算符
    76. return tmp;
    77. }
    78. // 日期-=天数
    79. Date& operator-=(int day)
    80. {
    81. if (day < 0)
    82. {
    83. *this += (-day);
    84. return *this;
    85. }
    86. _day -= day;
    87. while (_day <= 0)
    88. {
    89. //月让位
    90. --_month;
    91. //年让位
    92. if (_month == 0)
    93. {
    94. --_year;
    95. _month = 12;
    96. }
    97. _day += GetMonthDay(_year, _month);
    98. }
    99. return *this;
    100. }
    101. // 前置++
    102. Date& operator++()
    103. {
    104. *this += 1; //先+1
    105. return *this; //再返回,返回+1后的结果
    106. }
    107. // 后置++
    108. Date operator++(int) //int用于占位,表示后置++
    109. {
    110. Date tmp(*this); //拷贝一份
    111. *this += 1; //然后+1
    112. return tmp; //返回+1前的结果
    113. }
    114. // 前置--
    115. Date& operator--()
    116. {
    117. *this -= 1; //先-1
    118. return *this; //再返回,返回-1后的结果
    119. }
    120. // 后置--
    121. Date operator--(int) //int用于占位,表示后置--
    122. {
    123. Date tmp(*this); //拷贝一份
    124. *this -= 1; //然后-1
    125. return tmp; //返回-1前的结果
    126. }
    127. // >运算符重载
    128. bool operator>(const Date& d)
    129. {
    130. if (_year > d._year) //比较年
    131. {
    132. return true;
    133. }
    134. else if (_year == d._year && _month > d._month) //年相等比较月
    135. {
    136. return true;
    137. }
    138. else if (_year == d._year && _month == d._month && _day > d._day) //年月都相等比较日
    139. {
    140. return true;
    141. }
    142. else //前面的条件都不符合,说明日期比较小,返回false
    143. {
    144. return false;
    145. }
    146. }
    147. // ==运算符重载
    148. bool operator==(const Date& d)
    149. {
    150. return (_year == d._year) && (_month == d._month) && (_day == d._day);
    151. }
    152. // <运算符重载
    153. bool operator<(const Date& d)
    154. {
    155. //*this表示当前对象
    156. return !((*this == d) || (*this > d)); //逻辑非之后表示既不大于也不等于
    157. }
    158. // >=运算符重载
    159. bool operator >= (const Date& d)
    160. {
    161. return !(*this < d); //复用<运算符
    162. }
    163. // <=运算符重载
    164. bool operator <= (const Date & d)
    165. {
    166. return !(*this > d); //复用>运算符
    167. }
    168. // !=运算符重载
    169. bool operator != (const Date& d)
    170. {
    171. return !(*this == d); //复用==运算符
    172. }
    173. // 日期-日期 返回天数
    174. //法1:年-年,月-月,日-日,太过复杂
    175. //法2:找出较小的那个,一直累加直到等于大的那个,累加的次数就是之间的天数
    176. int operator-(const Date& d)
    177. {
    178. //找出较小的那个日期,这里先假设*this较小
    179. Date min = *this;
    180. Date max = d;
    181. int flag = -1; //用于表示最后相减结果的符号
    182. if (min > max) //假设不成立,互换
    183. {
    184. min = d;
    185. max = *this;
    186. flag = 1;
    187. }
    188. //小的进行累加,直到二者相等
    189. int ret = 0; //统计min累加的次数
    190. while (max > min)
    191. {
    192. min++;
    193. ret++;
    194. }
    195. return ret * flag; //最终累加的次数即为日期差
    196. }
    197. //打印数据
    198. void Print()
    199. {
    200. cout << _year << "年" << _month << "月" << _day << "日" << endl;
    201. }
    202. private:
    203. int _year;
    204. int _month;
    205. int _day;
    206. };

    以上,就是本期的全部内容啦🌸

    制作不易,能否点个赞再走呢🙏

  • 相关阅读:
    隐函数求导例题及解析
    简单介绍Map中的getOrDefault
    漫画sql数据分析
    如何实现redis的高可用?
    训练人工智能机器人的软实力
    Protobuf编码规则
    带你Java入门(Java系列1)
    Linux make/Makefile详解
    Windows使用ssh远程连接(虚拟机)Linux(Ubuntu)的方法
    循序渐进了解如何使用JSR303进接口数据校验
  • 原文地址:https://blog.csdn.net/m0_69909682/article/details/132537165