运算符重载
什么是运算符重载?
所谓重载,就是赋予新的含义。
函数重载
可以让一个函数名有多种功能,在不同情况下进行不同的操作。
运算符重载
也是一个道理,同一个运算符可以有不同的功能。
实际上,我们已经在不知不觉中使用了运算符重载。例如,+号可以对不同类型(int、float 等)的数据进行加法操作;<<既是位移运算符,又可以配合 cout 向控制台输出数据。C++ 本身已经对这些运算符进行了重载。
运算符重载的本质是函数重载:
运算符重载其实就是定义一个函数,在函数体内实现想要的功能,当用到该运算符时,编译器会自动调用这个函数,由这个函数实现其想要的功能。
好处:
虽然运算符重载所实现的功能完全可以用函数替代,但运算符重载使得程序的书写更加人性化,易于阅读。运算符被重载后,原有的功能仍然保留,没有丧失或改变。通过运算符重载,扩大了C++已有运算符的功能,使之能用于对象。
重载的注意事项:
-
重载不能改变运算符的优先级和结合性
-
重载不会改变运算符的用法,原有有几个操作数、操作数在左边还是在右边,这些都不会改变。例如+号总是出现在两个操作数之间,重载后也必须如此。
可以重载和不能重载的运算符
为什么不能
“?:”
假设可以重载,那么我们来看下列的代码:
exp1 ? exp2 : exp3
该运算符的含义是执行exp2和exp3中的一个,假设重载了,无法保证该运算符只执行某一个表达式的语义了,该运算符的跳转性质就不复存在了。所以,“?:”不能被重载。
“.”
假设可以重载,我们可以假设一种情况,创建一个对象,调用该对象的函数。
下面例子中,x.fun()就不知道是调用X还是Y的fun函数了。
“.”运算符的含义是引用对象成员,然而被重载后就不能保证了,导致运算符意义的混淆。
class Y{
void fun();
};
class X {
public:
Y *p;
Y &operator.(){
return *p;
}
void fun();
};
void g(X &x) {
x.fun();
}
“::”
该运算符只是在编译的时候域解析,而没有运算参与。根据重载的规则,如果重载该运算符,就赋予了新的语义,可能会出现混淆。
“sizeof”
不能被重载的原因主要是内部许多指针都依赖sizeof。
以成员函数还是普通的非成员函数的形式重载运算符?
下面的准则有助于我们做出抉择:
- 赋值(=)、下标([ ])、调用(( ))和成员访问箭头(->)运算符必须是成员。
- 改变对象状态的运算符或者与给定类型密切相关的运算符,如递增、递减和解引用运算符,通常应该是成员。
- 具有对称性的运算符可能转换任意一端的运算对象,例如算术、相等性、关系和位运算符等,因此它们通常应该是普通的非成员函数。
所以+、-、*、/、==、!=
应该以普通的非成员函数进行重载 - 复合赋值运算符一般来说应该是成员,但并非必须,这一点与赋值运算符略有不同。所以
+=、-=、*=、/=
应该以成员函数进行重载
下面举例解释:
1、为什么要以全局函数的形式重载 +、-、*、/、==、!=
看下面的例子:
#include <iostream>
using namespace std;
//复数类
class Complex{
public:
Complex(): m_real(0.0), m_imag(0.0){ }
Complex(double real, double imag): m_real(real), m_imag(imag){ }
Complex(double real): m_real(real), m_imag(0.0){ } //转换构造函数
public:
friend Complex operator+(const Complex &c1, const Complex &c2);
public:
double real() const{ return m_real; }
double imag() const{ return m_imag; }
private:
double m_real; //实部
double m_imag; //虚部
};
//重载+运算符
Complex operator+(const Complex &c1, const Complex &c2){
Complex c;
c.m_real = c1.m_real + c2.m_real;
c.m_imag = c1.m_imag + c2.m_imag;
return c;
}
int main(){
Complex c1(25, 35);
Complex c2 = c1 + 15.6;
Complex c3 = 28.23 + c1;
cout<<c2.real()<<" + "<<c2.imag()<<"i"<<endl;
cout<<c3.real()<<" + "<<c3.imag()<<"i"<<endl;
return 0;
}
运行结果:
40.6 + 35i
53.23 + 35i
上面的例子中,我们定义的operator+是一个全局函数而不是成员函数,这样做是为了保证 + 运算符
的操作数能够被对称的处理;换句话说,double 类型在 + 左边和右边都是正确的。第 30 行代码中,15.6 在 + 的右边,第 31 行代码中,28.23 在 + 的左边,它们都能够被顺利地转换为 Complex 类型,所以不会出错。
如果将operator+定义为成员函数,根据“+ 运算符具有左结合性”这条原则,Complex c2 = c1 + 15.6;会被转换为下面的形式:
Complex c2 = c1.operator+(Complex(15.6));
这就是通过对象调用成员函数,是正确的。
而对于Complex c3 = 28.23 + c1;,编译器会尝试转换为不同的形式:
Complex c3 = (28.23).operator+(c1);
很显然这是错误的,因为 double 类型并没有以成员函数的形式重载 +。
也就是说,以成员函数的形式重载 +,只能计算c1 + 15.6,不能计算28.23 + c1,这是不对称的
2、为什么要以成员函数的形式重载 +=等运算符
我们首先要明白,运算符重载的初衷是给类添加新的功能,方便类的运算,它作为类的成员函数是理所应当的,是首选的。
只有类似上面+运算符不能对称地处理数据时才以全局函数的形式进行重载、