运算符重载
如果我们想要知道两个数的大小,我们使用比较运算符例如>
就可以解决
那么现在有一个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++中的cout
,cin
即支持内置类型也支持自定义类型,只是要输入输出自定义类型需要我们自己定义重载函数
不同类型变量的流插入,流提取:
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;
}