目录
前言
https://blog.csdn.net/qq_42683011/article/details/102087764
基础应用:
重载运算符最需要考虑的即为参数与返回值问题
(这里以operator = 为例):
参数:
一般地,赋值运算符重载函数的参数是函数所在类的const类型的引用, 如:
MyStr& operator =(const MyStr& str);
加const是因为:
我们不希望在这个函数中对用来进行赋值的“原版”做任何修改。
加上const,对于const的和非const的实参,函数都能接受;如果不加,就只能接受非const的实参。
用引用是因为:
这样可以避免在函数调用时对实参的一次拷贝,提高了效率
注意:
上面的规定都只是推荐,可以不加const,也可以没有引用,甚至参数可以不是函数所在的对象。
返回值:
一般地,返回值是被赋值者的引用(但有时返回左值还是右值需要相当的考虑),即*this
MyStr& operator =(const MyStr& str);
这样在函数返回时避免一次拷贝,提高了效率。
更重要的,根据赋值运算符的从左向右的结合律, 可以实现连续赋值,即类似a=b=c
如果返回的是值,则执行连续赋值运算后后头得到的将是一个匿名副本, 为不可更改的右值,就是说是const类型, 再执行=c就会出错。
注意:
这也不是强制的,完全可以将函数返回值声明为void,然后什么也不返回,只不过这样就无法连续赋值,所以具体的返回值与参数的设定完全取决于需求, 是非常值得设计者考量的。
正文
1. 不能重载的运算符
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。函数名字为:关键字operator后面接需要重载的运算符符号。函数原型:返回值类型 operator操作符(参数列表)
注意:
1). 不能通过连接其他符号来创建新的操作符:比如operator@
2). 重载操作符必须有一个类类型参数
3). 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
4). 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
5).注意以下5个运算符不能重载。
运算符重载是具有特殊函数名的函数
函数名:operator运算符 //eg:operator==
有几个操作数,就有几个参数
参数:运算符操作数
返回值:运算符运算后的结果
2.怎么写运算符重载
下面简单写一下==的运算符重载:
#include<iostream>
using namespace std;
class Data
{
public:
Data(int year = 1, int month = 1, int day = 1)//自己写的全缺省构造
{
_year = year;
_month = month;
_day = day;
}
Data(const Data& d)//拷贝构造
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//private:
int _year;
int _month;
int _day;
};
bool operator==(Data d1, Data d2)//运算符重载函数
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
int main()
{
Data d1(2022, 10, 13);
Data d2(2022, 10, 13);
if (operator==(d1, d2))
{
cout << "==" << endl;
}
if (d1 == d2)
等价于if(operator==(d1, d2)),编译器自动处理成对应的运算符重载
{
cout << "==" << endl;
}
return 0;
}
发现如果使用运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?且这不就相当于额外写了一个函数,如果两个数相等就返回1,不相等就返回0。
那如果修改成以下代码?发现这样会报错。
#include<iostream>
using namespace std;
class Data
{
public:
Data(int year = 1, int month = 1, int day = 1)//自己写的全缺省构造
{
_year = year;
_month = month;
_day = day;
}
Data(const Data& d)//拷贝构造
{
_year = d._year;
_month = d._month;
_day = d._day;
}
bool operator==(Data d1, Data d2)//运算符重载函数
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1(2022, 10, 13);
Data d2(2022, 10, 13);
if (operator==(d1, d2))
{
cout << "==" << endl;
}
if (d1 == d2)
{
cout << "==" << endl;
}
return 0;
}
这样写会报错,==的参数太多了,是因为成员函数还有一个隐含的指针this。
所以需要进行修改成如下代码:
作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this。d1默认传给了this,this->_month == d._month,让this和d进行比较。
//bool operator==(Data d1, Data d2)
//if (operator==(d1, d2))
bool operator==(Data d)
运算符重载是具有特殊函数名的函数,编译器其实会处理成(Data* const this,Data d)
{
return _year == d._year//d1默认传给了this,
&& _month == d._month//this->_month = d._month//让this和d进行比较
&& _day == d._day;
}
d1默认传给了this,operator==(Data d)这个整体看成一个成员函数,而调用成员函数就是形式是(d1.函数名),然后对于其参数Data d,传参传入d2进行比较
int main()
{
Data d1(2022,10,13);
Data d2(2022, 10, 13);
//作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
if (d1.operator==(d2))//operator==(Data d)
{
cout << "==" << endl;
}
if (d1 == d2)
{
cout << "==" << endl;
}
return 0;
}
对于以上代码还可以进行优化:
因为传值传参会调用拷贝构造,所以最好加上引用,为了防止写反,最好加上const
#include<iostream>
using namespace std;
class Data
{
public:
Data(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Data(const Data& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
bool operator==(const Data& d)
//这样形参传给实参就不用调用拷贝构造函数了,因为只有自定义类型传值传参就会调用拷贝构造函数
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1(2022, 10, 13);
Data d2(2022, 10, 13);
if (d1 == d2)
{
cout << "==" << endl;
}
return 0;
}
3. 一元运算符和二元运算符:
在重载一个运算符为成员函数时,其参数表中没有任何参数,这说明该运算符是 ( )
A.无操作数的运算符
B.二元运算符
C.前缀一元运算符
D.后缀一元运算符
A.重载为成员函数时,其函数的参数个数与真实的函数参数个数会减少1个,减少的则通过this指针进行传递,所以无参 则说明有一个参数,故错误
B.无参成员函数相当于有一个参数的全局函数,不能是二元运算符
C.正确
D.区分前缀后缀时,后缀运算需要加一个int参数
前置和后置因为都是一样的,所以用int i的参数来区分,(注意i可以不写,形参不写代表着不用这个值或者说不接收)直接写int也可以 规定无参为前置++ ,这里只是改变了函数名的修饰规则以用来区分。
Data operator++(int i = 0);这样全缺省是错误的,因为i没有实际的意义,只是用来区分前置与后置++,或者说是全缺省和无参的函数在调用时区分不开。
4.思考一个问题
从下代码发现:当bool operator==(const Data& d1, const Data& d2)和bool operator==(const Data& d)同时存在时可以编译通过,为什么可以同时存在?
#include<iostream>
using namespace std;
class Data
{
public:
Data(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Data(const Data& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
bool operator==(const Data& d)
//这样形参传给实参就不用调用拷贝构造函数了,因为主要自定义类型传值传参就会调用拷贝构造函数
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
//private:
int _year;
int _month;
int _day;
};
bool operator==(const Data& d1, const Data& d2)
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
int main()
{
Data d1(2022, 10, 13);
Data d2(2022, 10, 13);
if (d1 == d2)
{
cout << "==" << endl;
}
return 0;
}
因为一个是全局函数,一个是类里面的函数,他们的函数名相同,没有关系。但是他会优先调用类里面的重载。可以通过上面代码的调试发现:
类成员函数和全局函数的对比:
1).类成员函数和全局函数的区别就是,一个是面向对象,一个是面向过程,这同样也是c与c++的区别;
2).类成员函数==(转成)==>全局函数:增加一个参数,增加的这个参数是代替this指针的;
3).全局函数==(转成)==>类成员函数:减少一个参数,减少的这个参数通过this指针隐藏。
5. 那么写好了==,>、<、=怎么写了?
以下以<为例子:bool operator<(const Data& d)
#include<iostream>
using namespace std;
class Data
{
public:
Data(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Data(const Data& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
bool operator<(const Data& d)
//这样形参传给实参就不用调用拷贝构造函数了,因为主要自定义类型传值传参就会调用拷贝构造函数
{
if ((_year < d._year)
|| (_year == d._year && _month < d._month)
|| (_year == d._year && _month == d._month && _day < d._day))
{
return true;
}
else
{
return false;
}
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1(2022, 9, 14);
Data d2(2022, 10, 14);
if (d1 < d2)
{
cout << "<" << endl;
}
return 0;
}
注意:=怎么写?依葫芦画瓢如下:
但是这样写其实不对,像赋值语句j = i,返回值其实是j,但是这样返回值是void,如果需要满足连续赋值k = j = i,就需要对返回值进行修改。
//d2 = d1; -> d2.operator=(&d2,d1)
void operator=(const Data& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
注意以下的区分:
Data d3(d1); //拷贝构造 -- 一个存在的对象去初始化另一个要创建的对象
d2 = d1; //赋值重载/复制拷贝 -- 两个已经存在的对象之间赋值
当这样赋值时会报错:
d3 = d2 = d1;
//二元“ = ”: 没有找到接受“void”类型的右操作数的运算符(或没有可接受的转换)
所以进行修改:
所以应该写成以下这样,需要满足值,因为对于d2 = d1;返回值应该是d2,但是要注意返回值怎么写
d2 = d1; -> d2.operator=(&d2,d1)
Data operator=(const Data& d)
但是我们发现这种写法是传值返回,
所有的传值返回和传值传参一样,会生成一个拷贝,
自定义类型传值会调用拷贝构造函数
进一步进行修改:
因为如果出了作用域返回值还在,可以用引用返回:注意d2 = d1,把d1赋给d2,返回值是d2,d2默认传给this。this是指针,所以返回*this得到指针本身。
Data& operator=(const Data& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
同样要注意,不排除写错了,自己给自己赋值,所以应该这样写:加上if(this != d),就是避免d2 =d1时写成d2 = d2,d2默认传给this的,d1传给const Data& d的,所以如果d = this就是自己给自己赋值了。同样这样的好处是,只会是权限的缩小,不会有权限的扩大。
Data& operator=(const Data& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
注意:赋值运算符在类中不显式实现时,编译器会生成一份默认的,此时用户在类外再将赋值运算符重载为全局的,就和编译器生成的默认赋值运算符冲突了,故赋值运算符只能重载成成员函数
总结
运算重载符号(C++)