当前位置:   article > 正文

冰冰学习笔记:运算符重载---日期类的实现_日期比较(运算符重载之类型转换)

日期比较(运算符重载之类型转换)

欢迎各位大佬光临本文章!!!

还请各位大佬提出宝贵的意见,如发现文章错误请联系冰冰,冰冰一定会虚心接受,及时改正。

本系列文章为冰冰学习编程的学习笔记,如果对您也有帮助,还请各位大佬、帅哥、美女点点支持,您的每一分关心都是我坚持的动力。

我的博客地址:bingbing~bang的博客_CSDN博客https://blog.csdn.net/bingbing_bang?type=blog

我的gitee:冰冰棒 (bingbingsupercool) - Gitee.comhttps://gitee.com/bingbingsurercool


系列文章推荐

冰冰学习笔记:《类与对象(上)》

冰冰学习笔记:《类与对象(中)》


目录

系列文章推荐

前言

1.日期类简介

2.日期类的内置函数

3.日期类比较运算符重载

3.1相等运算符重载

3.2大于运算符重载

3.3其他比较运算符的实现

4.日期类的加减运算符重载

4.1日期加等天数的重载

4.2日期加天数的重载

4.3日期减等天数的重载

4.4日期减天数的重载

4.5日期的前置与后置的加加减减

4.6日期与日期之间的相减

5.流插入流提取运算符重载


前言

        在类与对象对象的文章中我们提及了运算符重载的概念,并且介绍了类中自动生成的成员函数:赋值运算符重载。我们之前还学过函数重载,这两者有关系吗?并没有!运算符重载是让自定义类型可以用运算符,在使用运算符的时候转换成使用这个重载运算符。函数重载是允许同时存在函数名相同的函数。

        那么运算符重载在实际应用中究竟是什么样的呢?现在我们就以日期类的详细实现来对运算符重载进行各种应用。

1.日期类简介

        我们要实现的日期类是一个计算查看日期功能的小程序,里面需要判断日期相等、大小、以及实现日期加减天数,日期的前置后置加减操作等,这必然需要实现各种日期类的运算符重载。因为运算符只支持内置类型的操作,不支持自定义类型

2.日期类的内置函数

        日期类中并没有开辟空间,成员变量是年月日三个内置类型,因此我们只需要实现构造函数即可,析构函数,拷贝构造,赋值运算符重载都不需要实现。

        另外我们还需要实现一个检测函数,来确保我们自己进行初始化时输入的日期合法,这样在进行后面的操作时才不会出错。函数实现比较简单,只要保证年份大于等于1,月份在1到12月之间,天数满足每个月的天数即可。

        在实现检测函数时我们必然要获取到每个月份的天数,闰年的2月又与其他年份不一样,因此我们还需要实现获取月份天数的函数。这里我们将每个月的天数全部列出,存储在一个静态数组中,使用月份的下标就可以获得对应天数。这里有两个细节,数组设定为静态,在程序运行期间只会开辟一次空间,再次访问函数时并不会频繁开辟空间,因为静态数组函数调用后不会销毁。判断闰年时先进行月份判断,原因在于无论是不是闰年,只有二月存在差异,当月份是二月时才会实现闰年的判断,而不是先判断是否为闰年在判断是否为2月,提高代码效率。

        以上三个函数在创建对象以及后面运算符重载时会平凡调用,因此写在类中,让其变为内联函数。

  1. class Date
  2. {
  3. public:
  4. // 带参构造函数(缺省参数)
  5. Date(int year = 1900, int month = 1, int day = 1)//构造函数
  6. {
  7. _year = year;
  8. _month = month;
  9. _day = day;
  10. if ( !CheckDate() )//判断日期是否合理
  11. {
  12. Print();
  13. cout << "日期非法" << endl;
  14. }
  15. }
  16. //检查日期函数
  17. bool CheckDate()
  18. {
  19. if ( _year >= 1
  20. && _month > 0 && _month < 13
  21. && _day>0 && _day <= GetMonthDay(_year, _month) )
  22. {
  23. return true;
  24. }
  25. else
  26. {
  27. return false;
  28. }
  29. }
  30. int GetMonthDay(int year, int month)//--频繁调用,内联
  31. {
  32. static int arr[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
  33. //设计成静态,不会消失就不会频繁创建
  34. if ( month == 2
  35. && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) )
  36. {//先判断月,避免频繁判断闰年
  37. return 29;
  38. }
  39. else
  40. {
  41. return arr[month];
  42. }
  43. }
  44. private:
  45. int _year = 0;//声明,并非初始化,缺省参数
  46. int _month = 0;
  47. int _day = 0;
  48. };

3.日期类比较运算符重载

        一个类中究竟需要重载哪些运算符,主要是看这个类中哪些运算符有意义。例如重载日期加日期的运算符就是无意义的,而重载日期加天数就有意义。运算符重载函数我们进行声明与定义分开,避免类中代码冗余。

3.1相等运算符重载

        相等运算符重载比较简单,判断两个日期类是否相等无非是判断里面的成员年月日是否相等。由于我们并不会对传递过来的日期对象进行更改,可以对this指针加上const。

  1. bool Date::operator==(const Date& y)const
  2. {
  3. return _year == y._year
  4. && _month == y._month
  5. && _day == y._day;
  6. }

3.2大于运算符重载

        有时我们还会比较两个日期的大小,这时就需要实现比较运算符来对自定义类型进行比较。一个日期大于另一个日期,就需要分情况来判断。当年份大时,该日期大,年份相等月份大则大,月份相等天数大则大,否则就不满足大于条件。

  1. bool Date::operator>(const Date& y)const//对象不需要改变
  2. {
  3. if ( (_year > y._year)
  4. || (_year == y._year && _month > y._month)
  5. || (_year == y._year && _month == y._month && _day > y._day) )
  6. {
  7. return true;
  8. }
  9. else
  10. {
  11. return false;
  12. }
  13. }

3.3其他比较运算符的实现

        比较运算符不仅包含大于符号,还包含小于,不等于,小于等于,大于等于运算符,这些都需要我们一一进行逻辑编写吗?答案是否定的,我们并不用再去实现相同逻辑下的比较运算符,我们可以直接复用已经实现的等于与大于运算符。因此:任何一个类只需要写一个小于,等于或者大于,等于运算符即可,其余的比较运算符直接复用即可

        小于符号的实现无非就是大于等于的取反,不等于是等于的取反,大于等于就是即大于又等于,小于等于就是大于的取反。

  1. bool Date::operator<(const Date& y)const
  2. {
  3. return !((*this >= y));
  4. }
  5. bool Date::operator!=(const Date& y)const
  6. {
  7. return !(*this == y);
  8. }
  9. bool Date::operator>=(const Date& y)const
  10. {
  11. return (*this > y) || (*this == y);
  12. }
  13. bool Date::operator<=(const Date& y)const
  14. {
  15. return !(*this > y);
  16. }

4.日期类的加减运算符重载

        我们前面说过,日期加日期并没有意义,但是日期减日期有意义,可以计算两日期之间相差的天数。日期加减天数也具备意义,可以得到相应的日期。

4.1日期加等天数的重载

        日期加等上一个新的天数,得到的是新的日期,加等操作与加操作不同,加等操作this指针指向的对象将会改变,单纯的加减操作并不会改变日期本身。我们可以先实现加等操作然后直接加的时候可以复用加等,当然我们也可以先实现加,然后复用加等,但是并不推荐如此做。

        加等操作符返回的是this指针指向的对象,该对象并不会销毁,因此我们应使用引用返回来提高效率。前面的if语句是面对用户输入天数为负值的情况,day小于0就就意味着需要将日期减等day的相反数。

        加等的逻辑实现是先将用户输入的天数直接加到对象的_day中,然后循环判断_day是否大于当月天数,如果大于当月天数我们应该将天数减去当月天数,并且月份向前加一月。当月份大于12,此时需要将月份归到下一年的1月,年份加一年。

  1. Date& Date::operator+=(int day)//+=
  2. {
  3. if ( day < 0 )
  4. {
  5. return *this -= -day;
  6. }
  7. _day += day;
  8. while ( _day > GetMonthDay(_year, _month) )
  9. {
  10. _day -= GetMonthDay(_year, _month);
  11. _month++;
  12. if ( _month == 13 )
  13. {
  14. _month = 1;
  15. _year++;
  16. }
  17. }
  18. return *this;
  19. }

4.2日期加天数的重载

        日期加天数的重载虽然返回的是日期加减天数之后的结果,但是this指向的对象并不会改变,例如a=10,b=a+10,b虽然等于20但是a还是10,并没有改变。

        此时我们就需要使用拷贝构造对this指针进行拷贝,然后对拷贝后的结果进行加等操作,最后返回拷贝的对象。由于拷贝存在的对象是局部对象,出了函数作用域就会销毁,因此不能使用引用返回。

  1. Date Date::operator+(int day)const//+
  2. {
  3. Date ret(*this);//拷贝构造 Date ret= *this
  4. ret += day;
  5. return ret;
  6. }

        函数返回的是ret本身吗?并不是,函数为值返回,返回的并不是ret本身,而是ret的拷贝。那么整个加天数的函数调用过程就进行了两次拷贝,日期类这种成员少的还好,万一是一个很大的对象将会非常浪费资源。加等操作中并没有进行对象的拷贝操作,如果加等复用加进行,那么加等,加均需要使用频繁赋值对象,效率低下。 

4.3日期减等天数的重载

        日期减等操作与加等类似,返回类型也是引用类型。一开始也需要对天数的正负进行判断,如果为负值就进行加等操作。若不为负值,那就先将对象的天数减去输入天数,并判断是否小于等于0,日期没有零号,当小于等于零时则说明该月的日期不够减,需要上前一个月去借,那么对象指向的月份需要减1,此时还需要判断减1后的月份是否为0,如果为0则需要将月份归为上一年的12月,年份减1。随后需要让减完的月份天数加上该负值天数,得到该月份剩余的天数。

  1. Date& Date:: operator -= (int day)
  2. {
  3. if ( day < 0 )
  4. {
  5. return *this += -day;
  6. }
  7. _day -= day;
  8. while ( _day <= 0 )
  9. {
  10. _month--;
  11. if ( _month == 0 )
  12. {
  13. _year--;
  14. _month = 12;
  15. }
  16. _day += GetMonthDay(_year, _month);
  17. }
  18. return *this;
  19. }

4.4日期减天数的重载

        日期减天数情况与日期加天数类似,直接复用即可。

  1. Date Date:: operator - (int day) const//-
  2. {
  3. Date ret(*this);
  4. ret -= day;
  5. return ret;
  6. }

4.5日期的前置与后置的加加减减

        前置与后置的加加减减实际上就是对加等,减等的复用,前置加加,减减返回的是加减后的值,this指针指向的对象需要改变,因此直接对对象进行加等或减等操作返回即可。后置加加,减减返回的是加减之前的值,对this指向的对象进行改变后返回的是改动之前的值,需要进行拷贝构造后再返回。这里又会出现同样的问题,使用拷贝构造就必然需要使用传值返回,那就意味着需要进行对象拷贝,浪费效率,这也侧面说明了后置加加,减减操作符比前置效率低一点

        但是,无论是前置还是后置操作符,都只有一个操作对象,我们怎么区分是前置还是后置呢?为解决这个问题,C++对后置操作符重载时增加了一个参数int,但是并不会接收该参数,只是作为区分。

  1. Date& Date::operator ++ ()//前置
  2. {
  3. *this += 1;
  4. return *this;
  5. }
  6. Date Date::operator ++ (int)//后置
  7. {
  8. Date tmp(*this);
  9. *this += 1;
  10. return tmp;
  11. }
  1. Date& Date:: operator -- ()//前置
  2. {
  3. *this -= 1;
  4. return *this;
  5. }
  6. Date Date:: operator -- (int)//后置
  7. {
  8. Date tmp(*this);
  9. *this -= 1;
  10. return tmp;
  11. }

4.6日期与日期之间的相减

        日期与日期相减得到的是两日期之间的差值。这里我们提供两种计算方法,一种是我们并没有实现其他运算符重载的情况下,单就这一问题进行的计算。另一种则是复用我们的运算符进行实现。

(1)设定基准值两个日期到基准值的天数相减

        该种方式是采用两个日期与基准值1900年之间的差值进行计算相差天数。我们首先计算当前日期该年中的天数,如果month>0则将该月的天数加到day上,当然我们需要先将当前对象的天数加上,然后从上个月到1月之间的天数进行累加。然后从1900年开始,年份进行递增,直到与对象中的年份相同,其中闰年增加366天,其他年增加365天,然后返回累加的天数。最后回到主函数中对两个日期进行相减,返回差值。

  1. int Date::GetDay( Date& d)
  2. {
  3. int day = d._day;
  4. int month = d._month;
  5. int year = 1900;
  6. while ( --month > 0 )
  7. {
  8. day += GetMonthDay(d._year, month);
  9. }
  10. while ( year != d._year )
  11. {
  12. if ( (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) )
  13. {
  14. day += 366;
  15. }
  16. else
  17. {
  18. day += 365;
  19. }
  20. year++;
  21. }
  22. return day;
  23. }
  24. int Date::operator -(Date& y)
  25. {
  26. int day1 = GetDay(*this);
  27. int day2 = GetDay(y);
  28. int count = day1 - day2;
  29. if ( count < 0 )
  30. {
  31. return -count;
  32. }
  33. else
  34. {
  35. return count;
  36. }
  37. }

(2)复用其他运算符进行运算

        首先我们先找出两个日期中的大小,然后让小的日期进行按天数累加,直到与大的日期相等,返回累加的天数即可。

  1. int Date::operator -(Date& y)const
  2. {
  3. int flag = 1;
  4. Date max = *this;
  5. Date min = y;
  6. if ( *this < y )
  7. {
  8. max = y;
  9. min = *this;
  10. flag = -1;
  11. }
  12. int n = 0;
  13. while ( min != max )
  14. {
  15. ++min;
  16. ++n;
  17. }
  18. return flag * n;
  19. }

5.流插入流提取运算符重载

        运算符<<,>>是定义在ostream和istream两个类中的对象,流插入流提取运算符只能支持内置类型的使用,无法对自定义类型进行操作,因此我们还需要对其进行重载。

        由于两个运算符的左操作数并不是this指针,而是该类型的对象,因此我么需要实现在类外面,这里就需要使用友元关键字:friend 。将声明放在类中。我们还需要注意一点,cout和cin都是支持连续赋值的,因此我们需要返回赋值后的对象。

  1. inline ostream& operator <<(ostream& out, const Date& d)//插入
  2. {
  3. cout << d._year << "-" << d._month << "-" << d._day << endl;
  4. return out;
  5. }
  6. inline istream& operator >>(istream& in, Date& d)//提取
  7. {
  8. in >> d._year >> d._month >> d._day;
  9. assert(d.CheckDate());
  10. return in;
  11. }
  12. //类中的声明--部分
  13. class Date
  14. {
  15. friend ostream& operator <<(ostream& out, const Date& d);//友元
  16. friend istream& operator >>(istream& in, Date& d);

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/838161
推荐阅读
相关标签
  

闽ICP备14008679号