您现在的位置是:首页 >技术教程 >深入篇【C++】类与对象:运算符重载详解 -(下)+日期类的实现网站首页技术教程

深入篇【C++】类与对象:运算符重载详解 -(下)+日期类的实现

小陶来咯 2024-06-14 17:20:31
简介深入篇【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;
}

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。