目录
概念引入
对于内置类型的赋值编译器可以自行解决,而对于自定义的赋值
从编译器层面看,编译器无法做自己下定论规定自定义类型的赋值方式(里面的水很深)
从代码规范来看,运算符重载比通过编写相关运算符比较函数更有效率且更直观。使代码易于理解和维护。所以运算符重载(operator)应运而生。
运算符重载以函数的方式实现,但这里给出一个结论:
运算符重载≠函数重载
回顾函数重载的知识,不同类型,参数个数不同,类型不同顺序构成重载,而运算符重载是针对类这样的自定义类型的运算符进行重载。
给定一个日期类,我们先在全局定义来看看它的作用
运行结果
注意:
运算符重载的调用方式可以像函数一样调用,不过这样很麻烦,所以可以像内置类型一样使用运算符
<<的优先级要大于所有逻辑运算符,使用时要加上括号
我们刚才的运算符重载在类外定义,破坏了成员变量的私有性,有人说可以在类中定义get函数取它们的值,我试了一下发现比较麻烦,接下来我们把它放到类里面去。
这里发现错误:参数过多,因为类里的成员函数有一个隐含的this指针,所以我们在传参的时候只需传一个就行了。
修改后的代码
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
//输出
cout << d1.operator==(d2) << endl;
cout << (d1 == d2) << endl;
运算符重载注意事项
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
- .* :: sizeof ?: . 注意以上5个运算符不能重载(面试题)
代码复用
接下来我们实现一个<的运算符重载,这里给出两种代码。第一种可读性强,第二种更简便
bool 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 operator<(const Date& d)
{
return _year < d._year
|| (_year == d._year && _month < d._month)
|| (_year == d._year && _month == d._month && _day < d._day);
}
需要注意的是年月日的比较不是独立比较,而是在年>月>日这个前提下做判断的,那我们想实现一个>的话就可以将符号改一下就可以了,这或许还能接受,但是我要实现>= <= 又得添加大量代码,虽然无技术含量但是麻烦,所以这里推荐下面的方式改写代码
我们不是写了一个<(>)和一个=吗?通过这两个代码,就能实现所有判断运算符的复用,下面是见证奇迹的时刻!
bool operator<=(const Date& d)
{
return *this < d || *this == d;
}
bool operator!=(const Date& d)
{
return !(*this == d);
}
bool operator>(const Date& d)
{
return !(*this <= d);
}
可以看到这样写代码不要太爽
赋值重载
说到赋值,大家不要将拷贝构造 Date d1 = d2这种赋值与赋值重载混淆,前面我们也小小提到d1 = d2是不同与拷贝构造的,虽然两者都能实现,但世上没有两片相同的树叶。前者是在初始化对象时使用的,后者是在两个对象被实例化后使用的,相同的是,都通过调用函数的方式实现。
这里可以看到成功实现了赋值,有一个小点是这里不用引用传参是不会出现无限递归的,直接调用一次拷贝构造,但为了减少拷贝,推荐使用引用。
赋值运算符支持连续赋值,其原理是从右往左连续两个数赋值后产生一个返回值作为右操作数再赋给前一个对象,例如a = b = c,就是c的值赋给b后产生一个返回值让a接收。这里我们实现的赋值重载似乎不具备这个功能
Date& operator=(const Date& d)//赋值重载
{
if (this != &d)//防止d1=d1(智障),减少引发的空间浪费和深拷贝问题
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
[ ]重载
同样地,我们可以通过对自定义类型进行[ ]重载,使其输出其成员变量的数组。这里有两种定义方式,即两种重载,const和非const
定义个array数组类
class Array
{
public:
Array()
{
for (int i = 0; i < 10; ++i)
{
_a[i] = i;
}
}
private:
int _a[10] = {0};
int _size = 0;
};
关于初始化数组中的数,我们可以创建构造函数,给缺省,类外创建初始化函数或者直接在main函数中用for循环初始化 等方式。
int& operator[](int x)
{
assert(x < 10);
return _a[x];
}
const int& operator[](int x)const//const返回不改变其值;const构成重载
{
assert(x < 10);
return _a[x];
}
第一种是可以修改左值,第二种只能读不能写
题外话:一般const做返回值用来修饰指针与引用
默认赋值重载
作为六大成员函数之一,在不写赋值重载的前提下,编译器会自动生成,所以刚才我们可以直接使用默认生成的赋值重载。那什么情况下我们需要手动写呢?具体特性可以类比拷贝构造。