运算符重载实质上是函数的重载,作用于C++基本数据类型的运算符本来就是函数,我们是重载这些函数,不是只给某字符赋予一些功能。
我们重载运算符,可以改变运算符原本操作数的类型(操作数其实就是函数参数),但不能改变操作数的个数,以及运算符的优先级。也就是说不能像我们平时重载函数时那么随便。
单目运算符我们重载时只能传一个参数,双目只能传两个。注意成员函数自带一个参数——this指针。
有些运算符可以重载为成员函数,也可以重载为普通函数,也可以重载为友元函数。好像有个规律,操作数顺序是,有this指针就是this指针指向的对象为左操作数,另一个参数为右操作数;不重载为成员函数的话(没有this指针)就是第一个参数为左操作数,第二个为右操作数,但这样不能随意访问私有变量,写成友元更好。
一、重载+和-运算符
注意重载的时候要保证参与运算的两个对象,不发生改变。其实可以定义成const函数。
class complex
{
double real,imag;
public:
complex(int r,int i):real(r),imag(i){}
complex operator+(complex& B);//"+"重载为成员函数
friend complex operator-(complex& B);"-"重载为友员函数
}
complex complex::operator+(complex& B)
{
return complex(real+B.real,imag+B.imag);
}
complex operator-(complex& B)
{
return complex(real-B.imag,imag-B.imag);
}
complex r1(1,2),r2(3,3);
r1+r2;//就是r1.operator+(r2)
r1-r2;//就是operator-(r1,r2)
有两个问题:
1、为什么重载+,-返回值类型是 complex而不是complex&
2、什么情况下只能重载为友元函数。
首先第一个问题在return时,会创建一个新对象,作为和。如果返回新对象的引用,“+”函数结束之后就退出了,新对象也没了,何来引用呢。
如果我们如果我们希望1+r1能运行的话,就只能重载为友元函数
complex operator+(int x,complex& B)
{
return complex(x+B.imag,B.imag);
}
这就是上面说的参数顺序与运算符操作数的对应关系,complex& B在前,1+r1就是要报错。
二、赋值运算符重载。
赋值运算符只能重载为成员函数,返回值类型为引用,详见另一篇文章。
类中有一个默认赋值运算符,有指针悬挂问题。
三、流插入和提取运算符的重载
1、重载为成员函数
<< 的重载
class complex
{
double real,imag;
public:
complex(int r,int i):real(r),imag(i){}
ostream& operator<<(ostream& os);
}
ostream& complex::operator<<(ostream& os)
{
os<<real<<","<<imag;
return os;
}
complex r(1,2);
r<<cout;
上面输出r是不是很奇怪,哈哈只能这样写,还是参数顺序和运算符操作数顺序的对应关系决定的。所以<<不要重载为成员函数。
“>>”的重载
class complex
{
double real,imag;
public:
complex(int r,int i):real(r),imag(i){}
istream& operator>>(istream& os);
}
istream& complex::operator<<(istream& is)
{
is>>real>>imag;
return os;
}
complex r(1,1);
r>>cin;
所以>>也不要重载为成员函数。
2、重载为友员函数
可以把 ostream& 或 istream& 放在 complex& 前面,这样就可以这样了
cout<<r1;
cin>>r1;
因为返回值是引用,还可以
cout<<r1<<r2;
cin>>r1>>r2;
重载为普通函数与重载为友元函数一样,只是有无法访问私有成员变量的缺陷。
四、自增自减运算符的重载。
自增自减运算符都是单目运算符,但有前置和后置之分。也有运算式中的区别之分后置运算符是以运算前的值参与表达式运算的,重载时一定要考虑到这一点。c++规定,前置的++、–作为一元运算符重载;后置的++、–作为二元运算符重载(多写一个没用的int 型)
class complex
{
double real,imag;
public:
complex(int r,int i):real(r),imag(i){}
complex& operator++();//前置自增的重载
complex operator--(int);//后置自减的重载
}
complex& complex::operator++()
{
real++;imag++;
return *this;//自增自减是要改变自己的,return complex(real++,imag++);显然不合适,且这种临时对象不能返回引用
}
complex complex::operator--(int)
{
complex temp=*this;
real--;imag--;
return temp;//**让temp去参与运算,自己偷偷自减**
}
五、()与[ ]的重载。
重载()被称为函数对象,是双目运算符 。对象调用它时像函数一样。
[ ]被成为下标运算符,是双目运算符,后一个操作数为整型,表示下标。
class complex
{
double real,imag;
public:
complex(int r,int i):real(r),imag(i){}
double operator()(int id);//前置自增的重载
}
double complex::operator()(int id)
{
if(id==1)
return real;
else
return imag;
}
complex r(1,2);
cout<<r(1)//输出r.real
六、类型转换运算符的重载
1、不能对类型转换运算符重载函数指定返回值类型(返回值类型已经由重载的运算符类型决定)。
2、作用是将该类强制类型转换成我们需要的类型
3、自己定义的类型也可以重载,(即A中重载了B就可以将A类型强制转换成B类型)
4、explicit关键字放在重载函数前,可以禁止编译器隐式调用我的重载函数。
例1:int的重载
class A
{
public:
int a;
public:
explicit A(int a1):a(a1){ std::cout << "构造函数" << std::endl; }
operator int()const//类型转换运算符的重载不指定返回值
{
return a;
}
};
int main()
{
A a(3);
std::cout << a;
system("pause");
return 0;
}
在main函数中std::cout << a;
进行了隐式类型转换。很多时候这些转换是另人迷惑的,想禁止的话就在前面加上explicit
explicit operator int()const
{
return a;
}
这样一来就只能显示类型转换了
std::cout << a;//错误
std::cout << (int)a;//正确
例2:自定义类型B的重载
class B {
public:
int b;
explicit B(int _b):b(_b){}
};
class A
{
public:
int a;
public:
explicit A(int a1):a(a1){ std::cout << "构造函数2" << std::endl; }
explicit operator B()const
{
std::cout << "类型转换";
return B(2*a);
}
};
int main()
{
A a(3);
std::cout << ((B)a).b;
system("pause");
return 0;
}
总结:
1、不能定义新运算符
2、重载后符合自然,运行连续使用,加括号不会有bug。这要求考虑返回引用还是本身类型。
3、重载不能改变优先级和操作数(参数个数)
4、"."、“.*”、“::”、“?:”、“sizeof”不允许重载。
5、()、[ ] 、->、=必须重载为成员函数。
6、单参数构造函数,类型转换的重载,都会涉及到隐性类型转换,用explicit禁止隐式,只能显式。这是很提倡的做法,因为隐式转换很讨厌。