赞
踩
欢迎各位大佬光临本文章!!!
还请各位大佬提出宝贵的意见,如发现文章错误请联系冰冰,冰冰一定会虚心接受,及时改正。
本系列文章为冰冰学习编程的学习笔记,如果对您也有帮助,还请各位大佬、帅哥、美女点点支持,您的每一分关心都是我坚持的动力。
我的博客地址:bingbing~bang的博客_CSDN博客https://blog.csdn.net/bingbing_bang?type=blog
我的gitee:冰冰棒 (bingbingsupercool) - Gitee.comhttps://gitee.com/bingbingsurercool
在类与对象对象的文章中我们提及了运算符重载的概念,并且介绍了类中自动生成的成员函数:赋值运算符重载。我们之前还学过函数重载,这两者有关系吗?并没有!运算符重载是让自定义类型可以用运算符,在使用运算符的时候转换成使用这个重载运算符。函数重载是允许同时存在函数名相同的函数。
那么运算符重载在实际应用中究竟是什么样的呢?现在我们就以日期类的详细实现来对运算符重载进行各种应用。
我们要实现的日期类是一个计算查看日期功能的小程序,里面需要判断日期相等、大小、以及实现日期加减天数,日期的前置后置加减操作等,这必然需要实现各种日期类的运算符重载。因为运算符只支持内置类型的操作,不支持自定义类型。
日期类中并没有开辟空间,成员变量是年月日三个内置类型,因此我们只需要实现构造函数即可,析构函数,拷贝构造,赋值运算符重载都不需要实现。
另外我们还需要实现一个检测函数,来确保我们自己进行初始化时输入的日期合法,这样在进行后面的操作时才不会出错。函数实现比较简单,只要保证年份大于等于1,月份在1到12月之间,天数满足每个月的天数即可。
在实现检测函数时我们必然要获取到每个月份的天数,闰年的2月又与其他年份不一样,因此我们还需要实现获取月份天数的函数。这里我们将每个月的天数全部列出,存储在一个静态数组中,使用月份的下标就可以获得对应天数。这里有两个细节,数组设定为静态,在程序运行期间只会开辟一次空间,再次访问函数时并不会频繁开辟空间,因为静态数组函数调用后不会销毁。判断闰年时先进行月份判断,原因在于无论是不是闰年,只有二月存在差异,当月份是二月时才会实现闰年的判断,而不是先判断是否为闰年在判断是否为2月,提高代码效率。
以上三个函数在创建对象以及后面运算符重载时会平凡调用,因此写在类中,让其变为内联函数。
- class Date
- {
- public:
- // 带参构造函数(缺省参数)
- Date(int year = 1900, int month = 1, int day = 1)//构造函数
- {
- _year = year;
- _month = month;
- _day = day;
- if ( !CheckDate() )//判断日期是否合理
- {
- Print();
- cout << "日期非法" << endl;
- }
- }
- //检查日期函数
- bool CheckDate()
- {
- if ( _year >= 1
- && _month > 0 && _month < 13
- && _day>0 && _day <= GetMonthDay(_year, _month) )
- {
- return true;
- }
- else
- {
- return false;
- }
- }
- int GetMonthDay(int year, int month)//--频繁调用,内联
- {
- static int arr[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
- //设计成静态,不会消失就不会频繁创建
- if ( month == 2
- && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) )
- {//先判断月,避免频繁判断闰年
- return 29;
- }
- else
- {
- return arr[month];
- }
- }
- private:
- int _year = 0;//声明,并非初始化,缺省参数
- int _month = 0;
- int _day = 0;
- };
一个类中究竟需要重载哪些运算符,主要是看这个类中哪些运算符有意义。例如重载日期加日期的运算符就是无意义的,而重载日期加天数就有意义。运算符重载函数我们进行声明与定义分开,避免类中代码冗余。
相等运算符重载比较简单,判断两个日期类是否相等无非是判断里面的成员年月日是否相等。由于我们并不会对传递过来的日期对象进行更改,可以对this指针加上const。
- bool Date::operator==(const Date& y)const
- {
- return _year == y._year
- && _month == y._month
- && _day == y._day;
- }
有时我们还会比较两个日期的大小,这时就需要实现比较运算符来对自定义类型进行比较。一个日期大于另一个日期,就需要分情况来判断。当年份大时,该日期大,年份相等月份大则大,月份相等天数大则大,否则就不满足大于条件。
- bool Date::operator>(const Date& y)const//对象不需要改变
- {
- if ( (_year > y._year)
- || (_year == y._year && _month > y._month)
- || (_year == y._year && _month == y._month && _day > y._day) )
- {
- return true;
- }
- else
- {
- return false;
- }
- }
比较运算符不仅包含大于符号,还包含小于,不等于,小于等于,大于等于运算符,这些都需要我们一一进行逻辑编写吗?答案是否定的,我们并不用再去实现相同逻辑下的比较运算符,我们可以直接复用已经实现的等于与大于运算符。因此:任何一个类只需要写一个小于,等于或者大于,等于运算符即可,其余的比较运算符直接复用即可。
小于符号的实现无非就是大于等于的取反,不等于是等于的取反,大于等于就是即大于又等于,小于等于就是大于的取反。
- bool Date::operator<(const Date& y)const
- {
- return !((*this >= y));
- }
- bool Date::operator!=(const Date& y)const
- {
- return !(*this == y);
- }
- bool Date::operator>=(const Date& y)const
- {
- return (*this > y) || (*this == y);
- }
- bool Date::operator<=(const Date& y)const
- {
- return !(*this > y);
- }
我们前面说过,日期加日期并没有意义,但是日期减日期有意义,可以计算两日期之间相差的天数。日期加减天数也具备意义,可以得到相应的日期。
日期加等上一个新的天数,得到的是新的日期,加等操作与加操作不同,加等操作this指针指向的对象将会改变,单纯的加减操作并不会改变日期本身。我们可以先实现加等操作然后直接加的时候可以复用加等,当然我们也可以先实现加,然后复用加等,但是并不推荐如此做。
加等操作符返回的是this指针指向的对象,该对象并不会销毁,因此我们应使用引用返回来提高效率。前面的if语句是面对用户输入天数为负值的情况,day小于0就就意味着需要将日期减等day的相反数。
加等的逻辑实现是先将用户输入的天数直接加到对象的_day中,然后循环判断_day是否大于当月天数,如果大于当月天数我们应该将天数减去当月天数,并且月份向前加一月。当月份大于12,此时需要将月份归到下一年的1月,年份加一年。
- Date& Date::operator+=(int day)//+=
- {
- if ( day < 0 )
- {
- return *this -= -day;
- }
- _day += day;
- while ( _day > GetMonthDay(_year, _month) )
- {
- _day -= GetMonthDay(_year, _month);
- _month++;
- if ( _month == 13 )
- {
- _month = 1;
- _year++;
- }
- }
- return *this;
- }
日期加天数的重载虽然返回的是日期加减天数之后的结果,但是this指向的对象并不会改变,例如a=10,b=a+10,b虽然等于20但是a还是10,并没有改变。
此时我们就需要使用拷贝构造对this指针进行拷贝,然后对拷贝后的结果进行加等操作,最后返回拷贝的对象。由于拷贝存在的对象是局部对象,出了函数作用域就会销毁,因此不能使用引用返回。
- Date Date::operator+(int day)const//+
- {
- Date ret(*this);//拷贝构造 Date ret= *this
- ret += day;
- return ret;
- }
函数返回的是ret本身吗?并不是,函数为值返回,返回的并不是ret本身,而是ret的拷贝。那么整个加天数的函数调用过程就进行了两次拷贝,日期类这种成员少的还好,万一是一个很大的对象将会非常浪费资源。加等操作中并没有进行对象的拷贝操作,如果加等复用加进行,那么加等,加均需要使用频繁赋值对象,效率低下。
日期减等操作与加等类似,返回类型也是引用类型。一开始也需要对天数的正负进行判断,如果为负值就进行加等操作。若不为负值,那就先将对象的天数减去输入天数,并判断是否小于等于0,日期没有零号,当小于等于零时则说明该月的日期不够减,需要上前一个月去借,那么对象指向的月份需要减1,此时还需要判断减1后的月份是否为0,如果为0则需要将月份归为上一年的12月,年份减1。随后需要让减完的月份天数加上该负值天数,得到该月份剩余的天数。
- Date& 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 Date:: operator - (int day) const//-
- {
- Date ret(*this);
- ret -= day;
- return ret;
- }
前置与后置的加加减减实际上就是对加等,减等的复用,前置加加,减减返回的是加减后的值,this指针指向的对象需要改变,因此直接对对象进行加等或减等操作返回即可。后置加加,减减返回的是加减之前的值,对this指向的对象进行改变后返回的是改动之前的值,需要进行拷贝构造后再返回。这里又会出现同样的问题,使用拷贝构造就必然需要使用传值返回,那就意味着需要进行对象拷贝,浪费效率,这也侧面说明了后置加加,减减操作符比前置效率低一点。
但是,无论是前置还是后置操作符,都只有一个操作对象,我们怎么区分是前置还是后置呢?为解决这个问题,C++对后置操作符重载时增加了一个参数int,但是并不会接收该参数,只是作为区分。
- Date& Date::operator ++ ()//前置
- {
- *this += 1;
- return *this;
- }
- Date Date::operator ++ (int)//后置
- {
- Date tmp(*this);
- *this += 1;
- return tmp;
- }
- Date& Date:: operator -- ()//前置
- {
- *this -= 1;
- return *this;
- }
- Date Date:: operator -- (int)//后置
- {
- Date tmp(*this);
- *this -= 1;
- return tmp;
- }
日期与日期相减得到的是两日期之间的差值。这里我们提供两种计算方法,一种是我们并没有实现其他运算符重载的情况下,单就这一问题进行的计算。另一种则是复用我们的运算符进行实现。
(1)设定基准值两个日期到基准值的天数相减
该种方式是采用两个日期与基准值1900年之间的差值进行计算相差天数。我们首先计算当前日期该年中的天数,如果month>0则将该月的天数加到day上,当然我们需要先将当前对象的天数加上,然后从上个月到1月之间的天数进行累加。然后从1900年开始,年份进行递增,直到与对象中的年份相同,其中闰年增加366天,其他年增加365天,然后返回累加的天数。最后回到主函数中对两个日期进行相减,返回差值。
- int Date::GetDay( Date& d)
- {
- int day = d._day;
- int month = d._month;
- int year = 1900;
-
- while ( --month > 0 )
- {
- day += GetMonthDay(d._year, month);
- }
- while ( year != d._year )
- {
- if ( (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) )
- {
- day += 366;
- }
- else
- {
- day += 365;
- }
- year++;
- }
- return day;
- }
- int Date::operator -(Date& y)
- {
- int day1 = GetDay(*this);
- int day2 = GetDay(y);
- int count = day1 - day2;
- if ( count < 0 )
- {
- return -count;
- }
- else
- {
- return count;
- }
- }
(2)复用其他运算符进行运算
首先我们先找出两个日期中的大小,然后让小的日期进行按天数累加,直到与大的日期相等,返回累加的天数即可。
- int Date::operator -(Date& y)const
- {
- int flag = 1;
- Date max = *this;
- Date min = y;
- if ( *this < y )
- {
- max = y;
- min = *this;
- flag = -1;
- }
- int n = 0;
- while ( min != max )
- {
- ++min;
- ++n;
- }
- return flag * n;
- }
运算符<<,>>是定义在ostream和istream两个类中的对象,流插入流提取运算符只能支持内置类型的使用,无法对自定义类型进行操作,因此我们还需要对其进行重载。
由于两个运算符的左操作数并不是this指针,而是该类型的对象,因此我么需要实现在类外面,这里就需要使用友元关键字:friend 。将声明放在类中。我们还需要注意一点,cout和cin都是支持连续赋值的,因此我们需要返回赋值后的对象。
- inline ostream& operator <<(ostream& out, const Date& d)//插入
- {
- cout << d._year << "-" << d._month << "-" << d._day << endl;
- return out;
- }
- inline istream& operator >>(istream& in, Date& d)//提取
- {
- in >> d._year >> d._month >> d._day;
- assert(d.CheckDate());
- return in;
- }
-
- //类中的声明--部分
- class Date
- {
- friend ostream& operator <<(ostream& out, const Date& d);//友元
- friend istream& operator >>(istream& in, Date& d);
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。