目录
[2.1 实现其余的比较运算符](#2.1 实现其余的比较运算符)
前言:
C++中运算符重载的作用是可以让自定义类型的对象也可以进行相互运算,在c语言中,只有内置类型的变量或者常量可以使用运算符进行简单的计算,比如:定义两个变量int a = 12;int b = 20;可以实现a+b,a-b,a==b....,原因是a和b的类型都是内置类型(即系统提供的数据类型)。若要实现自定义类型对象的运算,只能把这些运算符封装成一个函数,在进行对对象的运算时调用对应的运算符重载函数,实现对象之间的运算。(为什么叫重载,因为在底层中,运算符本身也是一个函数,只是只针对内置类型做运算,现如今我们要实现对象的运算只能写一个专门针对对象的运算符函数,因此两个函数构造重载)
1、运算符重载的定义
运算符重载是一个函数,他的函数名很特殊是规定好的,即:(关键字)operator+要重载的运算符符号,两者构成函数名。函数名后面跟形参列表,并且具有返回类型,因为不管是那种运算符最后都会得出一个结果,这个结果就是运算符重载的返回值。
注意以下几点:
1、运算符重载不能有重载其他符号,比如:@。
2、运算符重载的形参至少有一个为自定义类型的参数。
3、这五个符号不能构成运算符重载:.* :: sizeof ?: . 。
4、运算符重载通常是以成员函数的形式出现,因此他的形参参数一般会比实现操作数少1个,原因是第一个参数是隐藏的this指针。
比如'<'符号的运算符重载格式如下:
cpp
class Date
{
//this=dt1,dt=dt2
bool operator<(const Date& dt)const//若想给this指针进行const修饰,则const加到此处
{
}
};
int main()
{
Date dt1;
Date dt2;
dt1 < dt2;//等价于:dt1.operator<(dt2)
return 0;
}
关系示意图如下:
由于有些运算符在进行运算时本质是不会改变对象的值(比如:< > >= <= ==),因此在实现这些运算符重载时,用const修饰形参,防止对象被改变,细节上也更加贴近这些运算符。
2、实现'<'、'=='的运算符重载
对两个日期类的对象进行比较和判断,具体代码如下:
cpp
#include<iostream>
using namespace std;
class Date//日期类
{
public:
Date(int year, int month, int day)//构造函数初始化
{
_year = year;
_month = month;
_day = day;
}
//小于号
//this=dt1,dt=dt2
bool operator<(const Date& dt)//bool类型,返回true表示dt1<dt2,false反之
{
if (_year < dt._year)
{
return true;
}
else if (_year == dt._year && _month < dt._month)
{
return true;
}
else if (_year == dt._year && _month == dt._month && _day < dt._day)
{
return true;
}
return false;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date dt1(2022, 2, 22);
Date dt2(2020, 2, 22);
cout<<(dt1 < dt2)<<endl;
cout<<(dt2 < dt1)<<endl;
return 0;
}
运行结果:
从结果可以看到,对象之间也可以使用小于号直接进行比较,比如dt1<dt2的结果是0,表示假,dt1>dt2结果是1,表示真。(为什么编译器在执行语句dt1<dt2时会跳到我们自己实现的运算符函数处,而不是调用系统自己的运算符重载,原因在于调用时实参是自定义类型的对象,而系统自己写的运算符函数的形参类型是内置类型,我们写的运算符函数的形参类型跟调用时刚好匹配,因此会调用我们写的运算符函数。)
实现判断dt1和dt2是否相等的运算符'==',代码如下:
cpp
#include<iostream>
using namespace std;
class Date//日期类
{
public:
Date(int year, int month, int day)//构造函数初始化
{
_year = year;
_month = month;
_day = day;
}
//小于号
//this=dt1,dt=dt2
bool operator<(const Date& dt)//bool类型,返回true表示dt1<dt2,false反之
{
if (_year < dt._year)
{
return true;
}
else if (_year == dt._year && _month < dt._month)
{
return true;
}
else if (_year == dt._year && _month == dt._month && _day < dt._day)
{
return true;
}
return false;
}
//判断是否相等运算符
bool operator==(const Date& dt)
{
return _year == dt._year && _month == dt._month && _day == dt._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date dt1(2022, 2, 22);
Date dt2(2020, 2, 22);
Date dt3(2022, 2, 22);
cout << (dt1 == dt2) << endl;
cout << (dt1 == dt3) << endl;
return 0;
}
运行结果:
从结果可以看到,dt1不等于dt2,所以返回0,表示假。dt1等于dt3,返回1表示真。从主函数中看,是直接用'=='号对两个对象进行判断的,实则上用'=='判断两个对象是否相等时,会跳到了我们写的运算符重载处,再进行对对象中各个成员变量的判断与比较(这时候调用的就是编译器自己的运算符函数了,因为都是内置类型的比较),最后返回一个结果给到调用处。
2.1 实现其余的比较运算符
当实现了'<'、'=='这两个运算符重载,其余的比较运算符重载想要实现就很简单了,可以直接复用'<'、'=='这两个运算符重载,完成其他的比较运算符重载。
比如,'<='的运算符重载实现如下:
cpp
#include<iostream>
using namespace std;
class Date//日期类
{
public:
Date(int year, int month, int day)//构造函数初始化
{
_year = year;
_month = month;
_day = day;
}
//小于号
//this=dt1,dt=dt2
bool operator<(const Date& dt)//bool类型,返回true表示dt1<dt2,false反之
{
if (_year < dt._year)
{
return true;
}
else if (_year == dt._year && _month < dt._month)
{
return true;
}
else if (_year == dt._year && _month == dt._month && _day < dt._day)
{
return true;
}
return false;
}
//判断相等
bool operator==(const Date& dt)
{
return _year == dt._year && _month == dt._month && _day == dt._day;
}
//小于等于
bool operator<=(const Date& dt)
{
return *this < dt || *this == dt;//复用以上两个运算符重载
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date dt1(2022, 2, 22);
Date dt2(2020, 2, 22);
Date dt3(2022, 2, 22);
cout << (dt1 <= dt2) << endl;
cout << (dt2 <= dt1) << endl;
cout << (dt1 <= dt3) << endl;
return 0;
}
运行结果:
大概逻辑就是'<='的运算符函数调用了 '<'、'=='的运算符函数,因此编译器在执行'<='的运算符函数会跳到 '<'、'=='的运算符函数处执行。大大的减少了程序代码量。
示意图如下:
因此其余的比较运算符也可以采用以上逻辑, 代码如下:
cpp
//大于等于
bool operator>=(const Date& dt)
{
return !(*this<dt);//复用'<'
}
//大于
bool operator>(const Date& dt)
{
return !(*this <= dt);//复用'<='
}
//判断不相等
bool operator!=(const Date& dt)
{
return !(*this == dt);//复用'=='
}
3、实现'+','-','+=','-='等运算符重载
日期用于加减的场景一般是计算多少天之后的日期是多少,或者多少天之前的日期是多少,以及两个日期之间隔了多少天。比如,计算2022.2.22的100天之后的日期是多少,先得到2月份的总天数,然后用22+100,如果超过了2月份的总天数,则用122减去2月份总天数,并且月份+1,如此循环。
'+='运算符重载代码:
cpp
#include<iostream>
#include<assert.h>
using namespace std;
class Date//日期类
{
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;
return;
}
}
//计算一个月有多少天
int GetMonthDay(int year, int month)//year作用是计算闰年
{
assert(month > 0 && month < 13);
int arr[] = { 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;
}
return arr[month];
}
void Print()const//打印日期
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
//日期+=天数
Date& operator+=(int x)
{
if (x < 0)//x为负数,表示减去x,可以调用-=运算符重载
{
//*this -= -x;//调用-=时,x要置为正数,否则又表示*this+x,因为此时x是负数
return *this;
}
_day += x;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month > 12)
{
_year++;
_month = 1;
}
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date dt1(2022, 2, 22);
Date dt2(2020, 2, 22);
dt1 += 100;
dt1.Print();
return 0;
}
运行结果:
这里注意的是,'+='运算符会改变自身的值,因此d1在+=100后,dt1的值会发生改变,但是'+'运算符就不会改变dt1的值了,所以在实现'+'运算符重载时,需要一个载体temp来代替dt1进行+=操作,然后再把再temp+=之后的值返回出去即可,这样一来dt1的值就不会被改变了。
'+'运算符重载代码如下:
cpp
#include<iostream>
#include<assert.h>
using namespace std;
class Date//日期类
{
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;
return;
}
}
//计算一个月有多少天
int GetMonthDay(int year, int month)//year作用是计算闰年
{
assert(month > 0 && month < 13);
int arr[] = { 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;
}
return arr[month];
}
void Print()const//打印日期
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
//日期+=天数
Date& operator+=(int x)
{
if (x < 0)//x为负数,表示减去x,可以调用-=运算符重载
{
//*this -= -x;//调用-=时,x要置为正数,否则又表示*this+x,因为此时x是负数
return *this;
}
_day += x;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month > 12)
{
_year++;
_month = 1;
}
}
return *this;
}
//拷贝构造
Date(const Date& da)
{
_year = da._year;
_month = da._month;
_day = da._day;
}
//日期+天数(复用)
Date operator+(int x)
{
Date temp(*this);//需要调用拷贝构造,创建一个临时变量temp
temp += x;//让temp复用+=
return temp;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date dt1(2022, 2, 22);
Date dt2 = dt1 + 100;
dt1.Print();
dt2.Print();
return 0;
}
运行结果:
创建载体的具体操作图:
'-'与'-='的逻辑也相同,代码如下:
cpp
//日期-=天数
Date& operator-=(int x)
{
if (x < 0)//如果-=一个负数,表示+=一个正数,因此可以调用+=函数
{
*this += -x;
return *this;
}
_day -= x;
while (_day <= 0)
{
_month--;
if (_month < 1)
{
_year--;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
//日期-天数(复用)
Date& operator-(int x)
{
Date temp = *this;//调用拷贝构造创建载体temp
temp -= x;//让载体进行-=
return temp;
}
4、前置、后置'++''--'
前置运算符和后置运算符重载的区别在于,前置运算符重载是无形参的,而后置运算符重载的形参必须给一个整形,作用仅仅是为了占位,目的只是为了区分前置和后置,例子如下:
cpp
//前置++
Date& operator++();
//后置++
Date& operator++(int);
'++'运算符表示自增1,因此可以调用'+='的重载,前置'++'运算符重载如下:
cpp
#include<iostream>
#include<assert.h>
using namespace std;
class Date//日期类
{
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;
return;
}
}
//计算一个月有多少天
int GetMonthDay(int year, int month)//year作用是计算闰年
{
assert(month > 0 && month < 13);
int arr[] = { 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;
}
return arr[month];
}
void Print()const//打印日期
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
//日期+=天数
Date& operator+=(int x)
{
if (x < 0)//x为负数,表示减去x,可以调用-=运算符重载
{
//*this -= -x;//调用-=时,x要置为正数,否则又表示*this+x,因为此时x是负数
return *this;
}
_day += x;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month > 12)
{
_year++;
_month = 1;
}
}
return *this;
}
//++日期
Date& operator++()
{
*this += 1;//复用+=
return *this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date dt1(2022, 2, 22);
++dt1;
dt1.Print();
return 0;
}
运行结果:
后置++要遵循先使用才++,因此要返回一个++之前的值,所以需要一个载体temp记录++之前的值,并且返回的是该载体temp,后置++的重载如下:
cpp
//日期++
Date& Date::operator++(int)
{
Date temp = *this;//要调用拷贝构造
*this += 1;//复用+=
return temp;
}
前置--和后置--的逻辑也一样,只不过把++换成了--,并且复用的是'-='运算符重载。'--'运算符重载如下:
cpp
//--日期
Date& operator--()
{
*this -= 1;
return *this;
}
//--日期
Date& operator--(int)
{
Date temp = *this;
*this -= 1;
return temp;
}
5、流提取、流插入重载
以上的打印日期的方式都是通过调用Print()函数而实现打印的,因此即使是'<<'和'>>'流插入和流提取的符号也可以进行重载,重载之后就可以实现直接通过'<<'和'>>'实现对象的打印和输入。
但是值得注意的是,流提取和流插入的顺序是固定的,即流在左边,打印对象在右边,这时候如果延用this指针则顺序会对不上。所以这里不能使用*this指针作为形参了,通常把流提取和流插入重载写到全局中,但是全局的函数访问不到Date类的私有域,也就无法将年、月、日打印出来。因此如果全局的函数想访问一个类的私有域,只能在该类中进行友元函数(friend)的声明,声明该全局函数是该类的"友元",就可以在全局函数中访问该类的私有域了。
流提取、流插入重载代码如下:
cpp
#include<iostream>
#include<assert.h>
using namespace std;
class Date//日期类
{
public:
//友元函数声明
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& out, Date& d);
//构造函数初始化
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;
return;
}
}
//计算一个月有多少天
int GetMonthDay(int year, int month)//year作用是计算闰年
{
assert(month > 0 && month < 13);
int arr[] = { 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;
}
return arr[month];
}
private:
int _year;
int _month;
int _day;
};
//声明在全局
//流插入
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
//流提取
istream& operator>>(istream& out, Date& d)
{
out >> d._year >> d._month >> d._day;
if (!(d._month > 0 && d._month < 13 && (d._day > 0 && d._day <= d.GetMonthDay(d._year, d._month))))
{
cout << "非法日期" << endl;
exit(-1);
}
return out;
}
int main()
{
Date dt1(2022, 2, 22);
cout << dt1 << endl;
Date dt2(1, 1, 1);
cin >> dt2;
cout << dt2 << endl;
return 0;
}
运行结果:
6、赋值重载'='
对象也可以通过赋值重载进行对象之间的"赋值",赋值重载代码如下:
cpp
//赋值重载
void operator=(const Date& dt)
{
_year = dt._year;
_month = dt._month;
_day = dt._day;
}
int main()
{
dt1=dt2;
//但是该赋值重载不支持连续赋值,即dt3=dt1=dt2;
}
所以为了可以连续赋值,赋值重载必须返回一个数,如下:
cpp
#include<iostream>
#include<assert.h>
using namespace std;
class Date//日期类
{
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;
return;
}
}
//计算一个月有多少天
int GetMonthDay(int year, int month)//year作用是计算闰年
{
assert(month > 0 && month < 13);
int arr[] = { 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;
}
return arr[month];
}
//赋值重载
Date& operator=(const Date& dt)
{
if (this != &dt)
{
_year = dt._year;
_month = dt._month;
_day = dt._day;
}
return *this;
}
void Print()const
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date dt1(2022, 2, 22);
Date dt2(1, 1, 1);
Date dt3(1,1,1);
dt3 = dt2 = dt1;
dt1.Print();
dt2.Print();
dt3.Print();
return 0;
}
运行结果:
最后赋值重载和拷贝构造以及构造三者要区分开:
对一个还没初始化的实例对象进行传参或者无传参调用构造函数进行初始化叫构造,而用一个已经实例化好的对象去初始化另一个还没被初始化的同类型对象叫拷贝构造。 而赋值重载的两个操作对象必须都是经过初始化了的。
用上面代码举例:
cpp
//构造
Date dt1(2022, 2, 22);
Date dt2(1, 1, 1);
Date dt3(1,1,1);
dt3 = dt2 = dt1;//赋值重载
Date dt4 = dt1;//拷贝构造
//Date dt4(dt1);//拷贝构造
结语:
以上就是关于运算符重载的讲解,运算符重载的本质就是想让对象可以进行和内置类型一样的运算,至于用何种逻辑进行运算就要看重载函数的具体实现了。最后希望本文可以给你带来更多的收获,如果本文对你起到了帮助,希望可以动动小指头帮忙点赞👍+关注😎+收藏👌!如果有遗漏或者有误的地方欢迎大家在评论区补充~!谢谢大家!!( ̄︶ ̄)↗