C++运算符重载

运算符重载

如果我们想要知道两个数的大小,我们使用比较运算符例如>就可以解决
那么现在有一个Date类,如果想要知道2个日期类的大小,我们可以写一个如下的函数,来判断大小

bool judge(const Date& d1, const Date& d2)
{
	if (d1._year > d2._year)
	{
		return true;
	}
	else if (d1._year == d2._year && d1._month > d2._month)
	{
		return true;
	}
	else if (d1._year == d2._year && d1._month == d2._month && _day > d2._day)
	{
		return true;
	}
	else
	{
		return false;
	}
}

其实这样其实判断相比较内置类型直接用>判断还是挺麻烦的

那么对于对象来说用可以直接用>比较吗?

答案是:可以,但是需要运算符重载

C++为了增加代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数
运算符重载函数也具有返回值类型,函数名,参数列表

函数名为: 关键字operator操作符(参数列表)

下面我们修改上面的judge函数完成一个判断日期大小的运算符重载函数

bool operator>(const Date& d1, const Date& d2)
{
	if (d1._year > d2._year)
	{
		return true;
	}
	else if (d1._year == d2._year && d1._month > d2._month)
	{
		return true;
	}
	else if (d1._year == d2._year && d1._month == d2._month && _day > d2._day)
	{
		return true;
	}
	else
	{
		return false;
	}
}

int main()
{
	Date d1(2000, 2, 2);
	Date d2(3000, 2, 2);
	cout << (d1 > d2) << endl;
	return 0;
}

当运行到d1 > d2时,自动就调用我们写的运算符重载函数了,这里编译会将d1 > d2转化为opertaor>(d1,d2),就和普通函数调用一样

这时我们发现一个问题这么写只有成员变量是公有的时候才有效,但是一个类的成员变量很小可能性是共有的,大部分都是私有的

所以我们只能把它修改为成员函数

bool Date::operator>(const Date& 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;
	}
	else
	{
		return false;
	}
}

可以看到,修改为成员函数后,参数列表中少了一个参数,其实这里并不是少了一个参数,而是参数列表中的第一个参数是this指针,隐藏了起来

在成员函数中,this指针总是作为第一个参数
对于操作符重载, 如果重载的是一个二元运算符,那么重载函数中第一个参数对应左运算符,第二个参数对应右运算符

当我们运行d1>d2时,这句代码转化为d1.operator>(d2)

操作符重载还需注意:

  • 不能通过连接其他符号来创建新的操作符,如:operator@
  • 重载操作符必须有一个类类型参数,也就是必须有一个自定义类型,不能像:bool operator>(const int &a1,const int& a2)
  • 是否要重载运算符,要看运算符对这个类是否有意义。比如,对于Date类来说,2个时间相加没有意义,所以没有必要进行+的运算符重载
  • 操作符是几个操作数,重载函数就要有几个参数(包括this指针)
  • 用于内置类型的运算符,其含义不能改变
  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  • .*::sizeof?:.,这五个运算符不能重载

接下来,我们实现一个Date,来进一步掌握运算符重载。


实现一个Date类

首先,Date类的成员变量都是内置类型,所以没有必要写析构函数和拷贝构造函数,所以我们只需写一个构造函数即可

#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

接下来分析一个,那些运算符对Date类有意义:

  • 日期之间可以比大小,所以<,>,<=,>=,==,!=可以进行运算符重载
  • 日期可以加减天数,得到以前或未来的日期,所以+,-,+=,-=,++,--可以进行运算符重载
  • 日期可以按照某种格式输入输出,所以流插入,流提取可以进行重载

比较运算符的重载

前面我们已经完成了>的重载:

bool Date::operator>(const Date& 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;
	}
	else
	{
		return false;
	}
}

==!=的重载写起来也十分容易:

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

bool Date::operator!=(const Date& d) const
{
	return !(*this == d);
}

可以>==结合起来,写出>=的重载:

bool Date::operator>=(const Date& d) const
{
	return *this > d || *this == d;
}

>=的反就是<,这样也简单地得到了<的重载:

bool Date::operator<(const Date& d) const
{
	return !(*this >= d);
}

同理,<==结合,得到<=

bool Date::operator<=(const Date& d)const
{
	return *this < d || *this == d;
}

+ - 运算符和 += -=运算符的重载

先实现+的重载
其思想就是:先把天数都加在_day上,然后依次减去_year,_month对应的天数,_month加一,重复这个操作,直到_day小于等于``_year,_month`对应的天数。

所以知道_year,_month对应的天数是关键,所以我们写一个函数,它的作用就是返回某一年某一月的天数

int Date::GetMonthDay(int year, int month)
{
	int days[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	if (((year % 4 == 0) && (year % 100 != 0)) || year % 400 == 0)
	{
		days[2] = 29;
	}

	return days[month];
}

接着按照上面的思路,写出+的重载:

Date& Date::operator+(int days)
{
	
	_day += days;
	while (_day > GetMonthDay(_year, _month))
	{
		_day = _day - GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
			_month = 1;
			_year++;
		}
	}
	return *this;
}

这里返回类型是Date&是为了实现连加
返回类型是引用,原因是*this的作用域在外部,this指向的对象在函数结束后不会被销毁,所以可以引用返回,提高效率

接着,我们发现这个函数改变了*this的值,但是在我们的思路中,一个日期加上某一天数,原日期是不变的
所以我们上面实现的其实是+=

Date& Date::operator+=(int days)
{
	
	_day += days;
	while (_day > GetMonthDay(_year, _month))
	{
		_day = _day - GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
			_month = 1;
			_year++;
		}
	}
	return *this;
}

+的重载是可以通过调用+=重载而实现:

Date Date::operator+(int days) 
{
	Date tmp = *this;
	tmp += days;
	return tmp;
}

这里的返回值类型是不是引用了,是因为函数里返回的是一个临时变量,不可以用引用返回

根据上面+=+的重载,同理,我们也可以得到-=-的重载:

Date& Date::operator-=(int days)
{

	_day -= days;
	while (_day <= 0)
	{
		_month--;
		if (_month <= 0)
		{
			_year--;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

Date Date::operator-(int days) 
{
	Date tmp = *this;
	tmp -= days;
	return tmp;
}

接下来还有一个问题:
就是如果有一下情况:d1+ (-1)d1- (-1),如果加减一个负数的情况下,我们上面的写法是满足不了的

所以还需改进:因为+-的重载是调用+=-=的重载,所以只需修改+-的重载即可:

Date& Date::operator+=(int days)
{
	if (days < 0)
	{
		*this -= (-days);
	}
	else
	{
		_day += days;
		while (_day > GetMonthDay(_year, _month))
		{
			_day = _day - GetMonthDay(_year, _month);
			_month++;
			if (_month == 13)
			{
				_month = 1;
				_year++;
			}
		}
	}
	return *this;
}

Date& Date::operator-=(int days)
{
	if (days < 0)
	{
		*this += (-days);
	}
	else
	{
		_day -= days;
		while (_day <= 0)
		{
			_month--;
			if (_month <= 0)
			{
				_year--;
				_month = 12;
			}
			_day += GetMonthDay(_year, _month);
		}
	}
	return *this;
}


前置++ 后置++ 重载

前置++和后置++都是医院运算符,为了让前置++和后置++有区分,所以C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递

增加这个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,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this+1,而temp是临时对象,因此只能以值的方式返回,不能返回引用。

前置和后置相比,前置更好,因为后置会多创造2个对象,降低效率。


流插入,流提取重载

对于输入输出,C语言中的 printf,scanf只支持内置类型,而C++中的coutcin即支持内置类型也支持自定义类型,只是要输入输出自定义类型需要我们自己定义重载函数

不同类型变量的流插入,流提取:
1.可以直接支持内置类型都是库里已经实现了的
2.可以直接支持自定义类型识别是因为函数重载

我们写一个流提取重载函数:

void Date::operator(ostream& out)
{
	out<<_year<<_month<<_day<<endl;
}

上面的写法其实不好
加入就就像上面一样,把流提取重载函数写成成员函数,那么函数参数列表中第一个参数其实是this,第一个参数对于左操作符,所以如果想正确调用这种写法的重载函数,就要这么写:

d1<<cout;//d1.operator<<(cout);

这样写不符合我们平时的输出习惯

所以流插入重载函数不能写成成员函数,函数要写成全局,但又访问不了成员变量了,对于这一点,有2个解决方法:

  • 有公有的GetYear(),GetMonth(),GetDay()
  • 将流插入设置为Date类的友元函数

下面我们用友元函数解决流插入函数

ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}

将返回值类型设为ostream&也是为了保证连续流插入

接着同理,可以写出流提取的重载函数:

istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

以上就基本实现了一个日期类
上面的实现还有几个不足的点:
1.对于非法日期输入,如输入的月份为负数或月份大于12等,我们在构造函数和流插入函数里加一层判断
2.对于一些成员函数中的this不会被修改,我们可以加const进行修饰一下

对于以上的不足,进行了改进,一下是全部代码:

#include <iostream>
using namespace std;
class Date
{

	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
public:
	Date(int year, int month, int day)
	{
		if (month > 0 && month < 13 && day>0 && day < GetMonthDay(year, month))
		{
			_year = year;
			_month = month;
			_day = day;
		}
		else
		{
			cout << "非法日期" << endl;
		}
		
	}
	bool operator>(const Date& d) const;
	bool operator==(const Date& d) const;
	bool operator!=(const Date& d) const;
	bool operator>=(const Date& d) const;
	bool operator<(const Date& d) const;
	bool operator<=(const Date& d) const;
	int GetMonthDay(int year, int month);

	Date& operator+=(int days);
	Date operator+(int days) const;

	Date& operator-=(int days);
	Date operator-(int days) const;

	Date& operator++();
	Date operator++(int);

	Date& operator--();
	Date operator--(int);

	int operator-(const Date& d) const;

	
private:
	int _year;
	int _month;
	int _day;
};




bool Date::operator>(const Date& d) const
{
	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;
	}
	else
	{
		return false;
	}
}

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

bool Date::operator!=(const Date& d) const
{
	return !(*this == d);
}
 
bool Date::operator>=(const Date& d) const
{
	return *this > d || *this == d;
}

bool Date::operator<(const Date& d) const
{
	return !(*this >= d);
}

bool Date::operator<=(const Date& d)const
{
	return *this < d || *this == d;
}


int Date::GetMonthDay(int year, int month)
{
	int days[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	if (((year % 4 == 0) && (year % 100 != 0)) || year % 400 == 0)
	{
		days[2] = 29;
	}

	return days[month];
}


Date& Date::operator+=(int days)
{
	if (days < 0)
	{
		*this -= (-days);
	}
	else
	{
		_day += days;
		while (_day > GetMonthDay(_year, _month))
		{
			_day = _day - GetMonthDay(_year, _month);
			_month++;
			if (_month == 13)
			{
				_month = 1;
				_year++;
			}
		}
	}
	

	return *this;
}

Date Date::operator+(int days) const
{
	Date tmp = *this;
	tmp += days;
	return tmp;
}

Date& Date::operator-=(int days)
{
	if (days < 0)
	{
		*this += (-days);
	}
	else
	{
		_day -= days;
		while (_day <= 0)
		{
			_month--;
			if (_month <= 0)
			{
				_year--;
				_month = 12;
			}
			_day += GetMonthDay(_year, _month);
		}
	}
	

	return *this;
}

Date Date::operator-(int days) const
{
	Date tmp = *this;
	tmp -= days;
	return tmp;
}



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

int Date::operator-(const Date& d) const
{
	Date max = *this;
	Date min = d;
	int target = 1;
	if (max < min)
	{
		max = d;
		min = *this;
		target = -1;
	}

	int n = 0;
	while (max != min)
	{
		min++;
		n++;
	}
	return n * target;
}


ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}

istream& operator>>(istream& in, Date& d)
{
	int year, month, day;
	in >> year >> month >> day;
	if (month > 0 && month < 13 && day>0 && day < d.GetMonthDay(year, month))
	{
		d._year = year;
		d._month = month;
		d._day = day;
	}
	else
	{
		cout << "非法日期" << endl;
	}
	return in;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

疯癫了的狗

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

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

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

打赏作者

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

抵扣说明:

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

余额充值