冰冰学习笔记:运算符重载---日期类的实现

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

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

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

我的博客地址: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月,提高代码效率。

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

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;
};

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

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

3.1相等运算符重载

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

bool Date::operator==(const Date& y)const
{
	return _year == y._year
		&& _month == y._month
		&& _day == y._day;
}

3.2大于运算符重载

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

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;
	}
}

3.3其他比较运算符的实现

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

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

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);
}

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

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

4.1日期加等天数的重载

        日期加等上一个新的天数,得到的是新的日期,加等操作与加操作不同,加等操作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;
}

4.2日期加天数的重载

        日期加天数的重载虽然返回的是日期加减天数之后的结果,但是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的拷贝。那么整个加天数的函数调用过程就进行了两次拷贝,日期类这种成员少的还好,万一是一个很大的对象将会非常浪费资源。加等操作中并没有进行对象的拷贝操作,如果加等复用加进行,那么加等,加均需要使用频繁赋值对象,效率低下。 

4.3日期减等天数的重载

        日期减等操作与加等类似,返回类型也是引用类型。一开始也需要对天数的正负进行判断,如果为负值就进行加等操作。若不为负值,那就先将对象的天数减去输入天数,并判断是否小于等于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;
}

4.4日期减天数的重载

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

Date Date:: operator - (int day) const//-
{
	Date ret(*this);
	ret -= day;
	return ret;
}

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

        前置与后置的加加减减实际上就是对加等,减等的复用,前置加加,减减返回的是加减后的值,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;
}

4.6日期与日期之间的相减

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

(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;
}

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

        运算符<<,>>是定义在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);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bingbing~bang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值