@[TOC](深入篇【C++】类与对象:运算符重载详解-(下)+日期类的实现💯干货满满!)
⏰一.运算符重载
内置类型(int /double…… )是可以之间进行运算符之间的比较的,因为编译器知道它们之间的比较规则,可以之间转化为指令。
那如果自定义类型能否之间进行运算符之间的比较呢?当然不能了,因为编译器是不知道这个自定义类型的规则是什么,不知道如何进行比较。
1.内置类型是可以之间比较的。
2.自定义类型是无法之间进行比较的。
那如何使自定义类型也能进行比较呢?这时C++给出了办法,让这个运算符重载成一个函数,当自定义类型进行比较时,其本质就是在调用重载函数。
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有器返回值类型,函数名字以及参数列表,其返回值类型与参数列表和普通函数类似。
比如我们想对下面这个自定义类型进行运算符的使用,该如何使用呢?
class Data
{
public:
Data(int year = 1, int month = 1, int day = 1)//构造函数要写,拷贝构造函数不用写,赋值重载不用写
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1(2023,5,1);
Data d2(2023,10,15);
}
我们知道并不是所有的运算符都能重载的,主要是那些对类有意义的运算符才可以重载。
🕓①.<=运算符重载
两个日期进行比较大小,是有意义的,所以和<=运算符是可以重载的。而我在运算符重载详解 -(上)中已经将部分运算符介绍,接下来继续介绍运算符重载。
那
<=运算符重载
该如何写呢?
是不是直接可以在 <运算符重载
的基础上直接改动即可呀。
下面是<运算符重载函数
bool Data:: operator<(const Data& 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 Data:: operator<(const Data& 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;
}
不过这里有一个更好的方法喔!!!
那就是函数【复用】,这个在很多场景下都很有用的。
怎么复用呢?我们是不是已经将<运算符重载 和==运算符重载写完了?
那我们就可以直接复用这两个运算符重载来写其他的比较运算符
1.<= 是不是就是 小于或者等于呀,或者大于的逆命题。
2.>=是不是就是 大于或者等于呀或者就是小于的逆命题。
3.!=是不是就是 等于的逆命题呀。
bool Data::operator<=(const Data& d)//直接复用<运算符重载和==运算符重载
{
return *this < d || *this == d;
}
🕒②.>=运算符重载
.>=运算符重载可以复用 >运算符和等于==运算符重载
或者是<运算符重载的逆命题:
bool Data::operator>=(const Data& d)
{
return !(*this < d);//复用<运算符重载 ,<的逆命题就是>=
}
🕑③. !=运算符重载
= =的逆命题就是!=所以可以直接复用= =运算符重载
bool Data::operator!=(const Data& d)
{
return !(*this == d);//直接复用==运算符重载的 -- 逆命题
}
总结:所以我们在写这些比较运算符,习惯先将<运算符重载和==运算符重载先写出来,然后其他比较运算符直接复用这两个运算符重载即可。
🕐④.+=运算符重载
我们知道日期加上一个日期是没有什么意义的,但是一个日期加上一个天数,这就有意义了,可以知道该天数后是什么日期。所以日期类是允许+=运算符重载的。
那+=运算符如何重载呢?
我们首先要知道如何进行日期的计算,当天数大于当前月的最大天数时,天数就要减去当前月份的最大天数,然后月份就要进一。
注意点:
1.当天数很大时,月份不断进一,但要注意月份不能超过12,当月份再进一应该是1,而不是13.
2.要注意每个月份的天数是不同的,要考虑闰年的2月和其他年的2月天数也是不同的。
所以我们首先手搓一个获得不同月份的天数的函数,要考虑闰年和不同年的2月不同。
int Data::GetMonDay(int year, int month)
{
int monday[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)&&month==2)
{
return 29;
}
else
return monday[month];
}
有一个点提一下,这里可以改进。
这里我们要的是每个月的天数,但每次调这个函数我们都要进行一次闰年的判断,判断然后再看是不是2月,这样很麻烦,这里的if条件句的目的是为了获得2月的天数,如果不是2月那就不要再去判断闰年了,所以我们可以将判断是否是2月放在前面先判断就可以避免每次都要判断是否是闰年了。
然后还有我们要频繁的调用这个函数,也就是这个月份日期数组每次调用都会开辟,太浪费效率了,所以我们可以让它变成静态区的,开辟一次就可以啦。不用每次调用都开辟一次。所以改进后的代码如下:
int Data::GetMonDay(int year, int month)
{
static int monday[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) )//先判断是否是2月,如果不是直接走开。
{
return 29;
}
else
return monday[month];
}
接下来我们就要进行这个对+=运算符的重载了
//这里可以用引用返回喔,因为返回的是对象本身,对象本身并不在该重载函数里面,所以函数结束,对象还存在。
Data& Data::operator+=(int day)
{
//第一步将天数全部加起来
_day += day;
while (_day > GetMonDay(_year, _month))//当天数大于当月最大天数时,就要进行循环
{
_day -= GetMonDay(_year, _month);//要减去当月的最大天数
++_month;//然后月份++
if (_month == 13)//要考虑月份不能超过12,当月份变成13时这是,正确的是1月,并且年也要加1
{
++_year;
_month = 1;
}
}
return *this;//最后将日期对象返回
}
🕦⑤.+运算符重载
+运算符和+=运算符的区别是什么呢?
int b=2;
b+=1;
b+1;
+=将变量本身改变了,而+并没有将变量改变。
所以同理,对于自定义类型,+=也将自定义变量本身发生改变,而+并不会改变自定义变量本身。
+运算符重载函数和+=运算符重载函数的区别就在于它们的返回值不同。
虽然最后结果相同,但是+运算符重载返回值是一个临时拷贝的对象,并不是真正改变的对象。
+=运算符重载函数的返回值是真正改变的对象。
//这里不可以使用引用返回,因为返回的是拷贝对象,函数结束,拷贝对象就销毁了
Data Data::operator+(int day)
{
Data tmp(*this);//拷贝一份,让tmp返回,*this没有改变
tmp._day += day;
while (tmp._day > GetMonDay(tmp._year, tmp._month))
{
tmp._day -= GetMonDay(tmp._year, tmp._month);
++tmp._month;
if (tmp._month == 13)
{
++_year;
tmp._month = 1;
}
}
return tmp;
}
我们注意到,+运算符重载函数里是让拷贝对象去实现+=运算符函数的相关操作,最后返回是拷贝对象
,真正的对象并没有修改。
这里我们也可以使用函数复用,+运算符重载直接复用+=运算符重载函数。
Data Data::operator+(int day) //+运算符重载 复用+=运算符重载
{
Data tmp(*this);//拷贝一份,让tmp返回,*this没有改变
tmp += day;//复用+=重载函数
这里其实就是tmp去调用+=运算符重载函数,tmp作为左参数。
return tmp;
}
其实+=运算符重载函数也可以直接复用+运算符重载
Data& Data::operator+=(int day)//用+=复用+
{
*this=*this + day;
//注意的是+是不修改本身的,所以对象变量+day还要再赋给对象变量
这里的+就是+运算符重载函数,*this对象去调用这个对象
return *this;
}
但是最好先实现+=运算符重载 再复用实现 +运算符重载,而不推荐用+=运算符重载来复用实现+运算符重载,为什么呢?
因为+运算符是不改变对象变量本身,所以必须要拷贝对象,也就是会调用拷贝构造函数,让拷贝对象去操作。并且最后返回的是拷贝对象,当函数结束时,该拷贝对象就销毁了,不能用引用做返回值。所以返回时又会调用一次拷贝构造构造。所以如果用+运算符重载来复用实现+=运算符重载,每调用一次+运算符重载该函数就要再调用两次拷贝构造。
而如果使用+=运算符重载来复用+运算符重载,就不需要调用拷贝构造了,因为+=运算符重载函数是会对对象本身修改,所以不需要拷贝对象,并且最后返回的是对象本身,可以用引用做返回,提高了效率哎,这不美滋滋?
所以呢,我们在写这类运算符时,最好先写+=运算符重载,然后再用+=运算符重载复用实现+运算符重载。
🕚⑥.-=运算符重载
有了日期加天数,那肯定也要有日期减天数呀。而且根据上面的介绍,我们是不是应该先从-=运算符重载写起,然后-运算符重载再复用-=运算符重载实现呀。
我们首先要分析,日期如何减一个天数
1.当日期的天数减去给定的天数结果小于等于0时,我们就需要对月改动,如果大于0那就不需要改动
2.当天数小于0时,我们需要借上个月的天数来还(注意是上个月,不是本月)
3.借着借着,要注意月份不能小于1了,当月份借到为0时,正确的应该是12月,并且年份要减一。
//跟+=运算符重载一样,这里-=运算符重载也可以使用引用做返回,因为最后返回的是对象本身,而不是对象的拷贝什么的。
Data& Data::operator-=(int day)
{
//首先将天数全部减去
_day -= day;
while (_day <= 0)//当天数小于等于0时,就要进循环来借上月份中的天数
{
--_month;//首先需要将月份减一,因为我们借的是上个月的天数,而不是本月的,本月没有天数辣,都用来减去天数变成负数了都,哪里的剩余天数。
if (_month == 0)//要注意月份不能小于1,当月份为0时
{
--_year;//年份要减一
_month = 12;//要将月份改成12
}
_day += GetMonDay(_year, _month);//借上个月份的天数来还。
}
return *this;
}
其实这个函数还有一点点问题,那就是下面这样:
int main()
{
Data d1(2023, 4, 26);
d1-=-10;
//日期减去一个天数,但这个天数却是负数,那结果是对的吗?
}
可以明显看出肯定错误了,因为天数怎么能超过31呢。
正确的场景是,如果天数为负数,那么-=天数应该变成了+=天数。
而+=天数就应该变成-=天数。
所以我们在-=运算符重载函数里再完善一点。
Data& Data::operator-=(int day)
{
if (day < 0)//如果天数小于0,那么-=天数就应该变成+=天数
{
return *this += -day;//这里的day还是负数所以加上负号让它变成正号。
}
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonDay(_year, _month);
}
return *this;
}
同理+=运算符重载函数里也应该这样写。
Data& Data::operator+=(int day)
{
if (day < 0)//如果天数小于0,那么+=天数就应该变成-=天数
{
return *this -= -day;//这里的day还是负数所以加上负号让它变成正号。
}
_day += day;
while (_day > GetMonDay(_year, _month))
{
_day -= GetMonDay(_year, _month);
++_month;
if (_month == 13)
{
++_year;
_month = 1;
}
}
return *this;
}
🕗⑦.-运算符重载(1)
有了-=运算符重载,我们还怕-运算符重载无法完成?笑话!
我们直接复用-=运算符重载即可,但要注意的是-运算符重载函数,需要拷贝对象,因为-运算符不修改对象本身喔。
Data Data::operator-(int day)
{
Data tmp(*this);//需要拷贝构造一个对象,让这个对象去操作
tmp -= day;//复用-=运算符重载
return tmp;
}
🕖⑧.-运算符重载(2)
日期-天数是有意义,可以知道天数之前是什么日期。
那日期-日期有没有意义呢?肯定有呀,这可以知道你小子从出生到现在活了多久。
这个就是对-运算符进行再重载。参数由天数(int类型)变成了,日期(日期类)。
因为函数名相同,但参数不同所以就可以构成重载。
那日期-日期如何计算呢?
data1 - data2 看起来很麻烦,涉及天数和月份甚至年份一起要改变。
不过我们可以不直接相减,换一种方法来求它们之间的天数。
1.只要让日期小的不断的加加到大的日期,每加一次就计数一次
2.当两个日期相同时,计数器所显示的就是它们之间的天数
3.不过要注意的是,一开始我们可不知道哪个日期大,哪个日期小,需要讨论。
int Data::operator-(Data& d)//这是日期-日期 -运算符重载函数
{
Data max = *this;//默认*this日期对象大
Data min = d;//d对象小
int flag = 1;
if (*this < d)//如果错了
{
max = d;
min = *this;
flag = -1;//那就将flag置为-1
}
int n = 0;//计数器
while (min != max)//让小的日期不断的加加到大的日期
{
++min;//每加一次
++n;//计数器计数一次
}
return n*flag;//最后计数器n就是它们之间的天数,而flag决定是正的还是负的。
}
🕕⑨.前置++运算符重载
C语言中++这个运算符分为前置++和后置++,前置++是先++再使用,而后置++是先使用后++。
在C++中如果要对自定义类型进行前置或者后置++,需要将它们重载成运算符函数。
C++规定前置++就正常按照运算符重载函数写,而后置++重载是要多增加一个int类型的参数,但是调用函数时该参数不用传递,编译器会自动传递。
//这里可以用引用做返回值,因为最后返回的是对象本身
Data& Data::operator++()//前置++运算符重载
{
*this += 1;//这里的+=其实就是+=运算符重载
return *this;
}
🕔⑩.后置++运算符重载
//这里不可以用引用左返回,因为返回的是拷贝对象,不是对象本身,函数结束,就销毁了
Data Data::operator++(int)//后置++运算符重载---比前置++多一个参数,为什么呢?因为这样才可以和前置++构成承载,因为函数名相同,参数不同才可以构成承载,因为不需要使用参数,所以可以只写类型
{
Data tmp(*this);//后置++,返回的是使用前的状态,因为后置++是先使用后++
*this += 1;//对象先使用,再++,也就是要将没++之前的对象返回过去。
return tmp;
}
⏰二.日期类的实现
TEST.h文件
#pragma once
#include <iostream>
using namespace std;
class Data
{
//析构也不用写
public:
Data(int year = 1, int month = 1, int day = 1)//构造函数要写,拷贝构造函数不用写,赋值重载不用写
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
Data(const Data& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
bool operator<(const Data& d);
bool operator==(const Data& d);
bool operator<=(const Data& d);
bool operator>(const Data& d);
bool operator>=(const Data& d);
bool operator!=(const Data& d);
Data operator++();//前置++
Data operator++(int);//后置++
int GetMonDay(int year, int month);
Data& operator+=(int day);
Data operator+(int day);
Data& operator-=(int day);
Data operator-(int day);
int operator-(Data& d);
private:
int _year;
int _month;
int _day;
};
```c
TEST.c文件
```c
bool Data:: operator<(const Data& 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 Data::operator==(const Data& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
bool Data::operator<=(const Data& d)//直接复用上面的
{
return *this < d || *this == d;
}
bool Data::operator>(const Data& d)
{
return !(*this <= d);
}
bool Data::operator>=(const Data& d)
{
return !(*this < d);
}
bool Data::operator!=(const Data& d)
{
return !(*this == d);
}
int Data::GetMonDay(int year, int month)
{
static int monday[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };//频繁调用
/*if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)&&month==2)*/
if (month == 2&&(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) )
{
return 29;
}
else
return monday[month];
}
//Data& Data::operator+=(int day)//用+复用+=-最好先实现+= 再复用实现 +
//{
// *this=*this + day;
//
// return *this;
//}
Data& Data::operator+=(int day)
{
if (day < 0)
{
return *this -= -day;
}
_day += day;
while (_day > GetMonDay(_year, _month))
{
_day -= GetMonDay(_year, _month);
++_month;
if (_month == 13)
{
++_year;
_month = 1;
}
}
return *this;
}
Data Data::operator+(int day)
{
Data tmp(*this);//拷贝一份,让tmp返回,*this没有改变
tmp._day += day;
while (tmp._day > GetMonDay(tmp._year, tmp._month))
{
tmp._day -= GetMonDay(tmp._year, tmp._month);
++tmp._month;
if (tmp._month == 13)
{
++_year;
tmp._month = 1;
}
}
return tmp;
}
//Data Data::operator+(int day) //+ 复用+=
//{
// Data tmp(*this);//拷贝一份,让tmp返回,*this没有改变
// tmp += day;//复用+=重载函数
// return tmp;
//}
Data Data::operator++()//前置++运算符重载
{
*this += 1;
return *this;
}
Data Data::operator++(int)//后置++运算符重载
{
Data tmp(*this);
*this += 1;
return tmp;
}
Data& Data::operator-=(int day)
{
if (day < 0)
{
return *this += -day;
}
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonDay(_year, _month);
}
return *this;
}
Data Data::operator-(int day)
{
Data tmp(*this);
tmp -= day;
return tmp;
}
//d1 - d2
int Data::operator-(Data& d)
{
Data max = *this;
Data min = d;
int flag = 1;
if (*this < d)
{
max = d;
min = *this;
flag = -1;
}
int n = 0;
while (min != max)
{
++min;
++n;
}
return n*flag;
}