5.3 C++用顶层函数重载操作符
参考:http://www.weixueyuan.net/view/6381.html
总结:
可以将操作符重载函数声明为顶层函数。
如果以顶层函数的形式重载操作符时,二元操作符重载函数必须有两个参数,一元操作符重载必须有一个参数。
加法操作符重载函数的函数头complex operator+(const complex & A, const complex &B),首先因为加法操作符重载后可以计算复数的加法,返回的仍然是一个复数。
需要注意的是指针操作符“->”、下标操作符“[]”、函数调用操作符“()”和赋值操作符“=”只能以成员函数的形式进行操作符重载。
以顶层函数的形式重载操作符,从函数实现上来看其实现相对于以类成员函数的形式实现起来要复杂一些,因为在类外无法直接访问类的私有成员变量。但是以顶层函数的形式来重载操作符有自身的优势。如下。
以类成员函数的形式进行操作符重载,操作符左侧的操作数必须为类对象;而以顶层函数的形式进行操作符重载,只要类中定义了相应的转型构造函数,操作符左侧或右侧的操作数均可以不是类对象,但其中必须至少有一个类对象,否则调用的就是系统内建的操作符而非自己定义的操作符重载函数了。
如果我们将操作符重载函数这些顶层函数声明为类的友元函数,那么就可以直接访问类的私有成员变量了。
采用友元函数的形式进行操作符重载,如此实现既能继承操作符重载函数是顶层函数的优势,同时又能够使操作符重载函数实现起来更简单。
---------------------------
在前面两节中,我们是将操作符重载函数声明为类的成员函数,其实除了能将操作符重载函数声明为类的成员函数之外,我们也可以将操作符重载函数声明为顶层函数。在前面将操作符重载函数声明为类成员函数时,我们不断强调二元操作符,其函数参数为一个,一元操作符重载函数不需要函数参数。但是一旦将操作符重载函数声明为顶层函数时,则必须至少有一个类对象参数,否则的话编译器无法区分操作符是系统内建的还是程序设计人员自己定义的,有了一个类对象参数之后,系统则会根据情况调用内建或自定的操作符。如果以顶层函数的形式重载操作符时,二元操作符重载函数必须有两个参数,一元操作符重载必须有一个参数。
例1:
#include <iostream> using namespace std; class complex { public: complex(); complex(double a); complex(double a, double b); double getreal() const { return real; } double getimag() const { return imag; } void setreal(double a){ real = a; } void setimag(double b){ imag = b; } void display()const; private: double real; //复数的实部 double imag; //复数的虚部 }; complex::complex() { real = 0.0; imag = 0.0; } complex::complex(double a) { real = a; imag = 0.0; } complex::complex(double a, double b) { real = a; imag = b; } //打印复数 void complex::display()const { cout<<real<<" + "<<imag<<" i "; } //重载加法操作符 complex operator+(const complex & A, const complex &B) { complex C; C.setreal(A.getreal() + B.getreal()); C.setimag(A.getimag() + B.getimag()); return C; } //重载减法操作符 complex operator-(const complex & A, const complex &B) { complex C; C.setreal(A.getreal() - B.getreal()); C.setimag(A.getimag() - B.getimag()); return C; } //重载乘法操作符 complex operator*(const complex & A, const complex &B) { complex C; C.setreal(A.getreal() * B.getreal() - A.getimag() * B.getimag() ); C.setimag(A.getimag() * B.getreal() + A.getreal() * B.getimag() ); return C; } //重载除法操作符 complex operator/(const complex & A, const complex & B) { complex C; double square = A.getreal() * A.getreal() + A.getimag() * A.getimag(); C.setreal((A.getreal() * B.getreal() + A.getimag() * B.getimag())/square); C.setimag((A.getimag() * B.getreal() - A.getreal() * B.getimag())/square); return C; } int main() { complex c1(4.3, -5.8); complex c2(8.4, 6.7); complex c3; c3 = c1 + c2; cout<<"c1 + c2 = "; c3.display(); cout<<endl; c3 = c1 - c2; cout<<"c1 - c2 = "; c3.display(); cout<<endl; c3 = c1 * c2; cout<<"c1 * c2 = "; c3.display(); cout<<endl; c3 = c1 / c2; cout<<"c1 / c2 = "; c3.display(); cout<<endl; return 0; }
如本例所示,本例则是用顶层函数重载的加法、减法、乘法和除法操作符,使之分别具有加减乘除功能。因为是以顶层函数的形式重载操作符的,因此类中没有声明操作符重载函数。为了能够在类外操作real和imag两个数据成员,我们为类添加了getimag、getreal、setimag和setreal函数。我们以加法操作符的重载为例来看普通操作符重载函数如何作为顶层函数。
加法操作符重载函数的函数头complex operator+(const complex & A, const complex &B),首先因为加法操作符重载后可以计算复数的加法,返回的仍然是一个复数,因此该函数的返回值仍然是complex。操作符重载函数参数为两个complex类对象的引用,加法操作符为二元操作符,因此必须要有两个操作数,因此函数有两个参数。函数体部分比较简单,只是使用getimag、getreal、setimag和setreal四个函数来实现复数的加法操作而已。
其它普通操作符的重载与例1中的加法操作符重载类似。如果我们重载的是一元操作符,则函数需要有一个参数。
例2:
class test { //...... }; test operator!(test & A) { //...... }
本例中以顶层函数的形式重载非操作符符,因为其为一元操作符,故而函数有一个参数。
以顶层函数的形式重载操作符,其调用方法与普通函数调用类似。
例如本节中的例1中的复数类,我们调用加法重载函数时可以采用如下方法:
complex c1, c2, c3;
c3 = operator+( c1, c2);
这样的函数调用方法和普通的函数调用方法一样,但是由于operator关键字的作用,我们还可以采用另外一种我们熟知的调用方法:
c3 = c1 + c2;
这种调用方法和先前以类成员函数的形式重载操作符调用方法一直。本节例1中也都是采用这种简单明了的调用方法。
需要注意的是指针操作符“->”、下标操作符“[]”、函数调用操作符“()”和赋值操作符“=”只能以成员函数的形式进行操作符重载。
以顶层函数的形式重载操作符,从函数实现上来看其实现相对于以类成员函数的形式实现起来要复杂一些,因为在类外无法直接访问类的私有成员变量。但是以顶层函数的形式来重载操作符有自身的优势,我们来看下面的示例。
例3:
#include <iostream> using namespace std; class complex { public: complex(); complex(double a); complex(double a, double b); complex operator+(const complex & A)const; complex operator-(const complex & A)const; complex operator*(const complex & A)const; complex operator/(const complex & A)const; void display()const; private: double real; //复数的实部 double imag; //复数的虚部 }; complex::complex() { real = 0.0; imag = 0.0; } complex::complex(double a) { real = a; imag = 0.0; } complex::complex(double a, double b) { real = a; imag = b; } //打印复数 void complex::display()const { cout<<real<<" + "<<imag<<" i "; } //重载加法操作符 complex complex::operator+(const complex & A)const { complex B; B.real = real + A.real; B.imag = imag + A.imag; return B; } //重载减法操作符 complex complex::operator-(const complex & A)const { complex B; B.real = real - A.real; B.imag = imag - A.imag; return B; } //重载乘法操作符 complex complex::operator*(const complex & A)const { complex B; B.real = real * A.real - imag * A.imag; B.imag = imag * A.real + real * A.imag; return B; } //重载除法操作符 complex complex::operator/(const complex & A)const { complex B; double square = A.real * A.real + A.imag * A.imag; B.real = (real * A.real + imag * A.imag)/square; B.imag = (imag * A.real - real * A.imag)/square; return B; } int main() { complex c1, c2(15.5, 23.1); c1 = c2 + 13.5; c1 = 13.5 + c2; return 0; }
本例中是以成员函数的形式进行操作符重载的,在主函数中我们定义了两个complex复数类的对象,语句“c1 = c2 + 13.5;”是将c2与一个double类型的数据相加,我们可以将其理解为:
c1 = c2.operator+(13.5);
因为我们在类中定义了一个只带一个参数的构造函数complex(double a);,这个构造函数其实可以视为转型构造函数,它可以将double类型转换为一个complex类对象。因此 “c1 = c2 + 13.5;”语句其实也是相当于两个复数类对象相加。当然,如果在类中没有定义complex(double a);这样一个只带一个参数的构造函数,那么这一句也是有语法问题的,因为我们重载的加法只适用于两个complex类对象相加,而系统内建的又只能用于两个普通数据类型的操作数相加,一个complex类对象和一个普通数据类型的操作数相加,系统是无法去处理这样的异常情况的。
我们再来看一下后面一个语句“c1 = 13.5 + c2;”,这一语句我们可以将其理解为:
c1 = 13.5.operator+(c2);
如此一来,这一句的问题非常明显,13.5只是一个double类型的常数,它不是类对象,因此也不可能有调用operator+的能力。虽然我们在类中定义了一个具有一个参数的构造函数,但是编译器将语句“c1 = 13.5 + c2;”理解成“c1 = 13.5.operator+(c2);”并不会将13.5转换成一个complex类对象,因为编译器遇到这种情况并不会产生一种很智能的处理,同样它也并不知道程序设计人员的意图。所以例3中语句“c1 = 13.5 + c2;”是有语法错误的。
例4:
#include <iostream> using namespace std; class complex { public: complex(); complex(double a); complex(double a, double b); double getreal() const { return real; } double getimag() const { return imag; } void setreal(double a){ real = a; } void setimag(double b){ imag = b; } void display()const; private: double real; //复数的实部 double imag; //复数的虚部 }; complex::complex() { real = 0.0; imag = 0.0; } complex::complex(double a) { real = a; imag = 0.0; } complex::complex(double a, double b) { real = a; imag = b; } //打印复数 void complex::display()const { cout<<real<<" + "<<imag<<" i "; } //重载加法操作符 complex operator+(const complex & A, const complex &B) { complex C; C.setreal(A.getreal() + B.getreal()); C.setimag(A.getimag() + B.getimag()); return C; } //重载减法操作符 complex operator-(const complex & A, const complex &B) { complex C; C.setreal(A.getreal() - B.getreal()); C.setimag(A.getimag() - B.getimag()); return C; } //重载乘法操作符 complex operator*(const complex & A, const complex &B) { complex C; C.setreal(A.getreal() * B.getreal() - A.getimag() * B.getimag() ); C.setimag(A.getimag() * B.getreal() + A.getreal() * B.getimag() ); return C; } //重载除法操作符 complex operator/(const complex & A, const complex & B) { complex C; double square = A.getreal() * A.getreal() + A.getimag() * A.getimag(); C.setreal((A.getreal() * B.getreal() + A.getimag() * B.getimag())/square); C.setimag((A.getimag() * B.getreal() - A.getreal() * B.getimag())/square); return C; } int main() { complex c1, c2(15.5, 23.1); c1 = c2 + 13.5; c1 = 13.5 + c2; return 0; }
我们再来看一下例4,这个例子则是以顶层函数的形式定义操作符重载函数。我们同样来看主函数,主函数定义了c1和c2两个complex类对象。先来看一下语句“c1 = c2 + 13.5;”,这个语句可以理解如下:
c1 = operator+(c2, 13.5);
因为我们在顶层函数中定义了complex operator+(const complex & A, const complex &B)函数,系统在执行“c1 = operator+(c2, 13.5);”时找到了对应的顶层函数,但是发现参数不对,但是可以通过类的构造函数将13.5转换成complex类对象,如此就满足operator+函数的调用条件了,故而这一句是没有问题的。
我们再来看一下语句“c1 = 13.5 + c2;”,这一语句可以理解为:
c1 = operator+(13.5, c2);
这一句的执行与“c1 = operator+(c2, 13.5);”是一样的,它可以利用类的构造函数将13.5转换为complex类对象,因此这一句也是可以正确执行的。
从例3和例4两个例子中,我们不难看出虽然实现麻烦的以顶层函数的形式进行操作符重载的优势所在了。我们总结一下,以类成员函数的形式进行操作符重载,操作符左侧的操作数必须为类对象;而以顶层函数的形式进行操作符重载,只要类中定义了相应的转型构造函数,操作符左侧或右侧的操作数均可以不是类对象,但其中必须至少有一个类对象,否则调用的就是系统内建的操作符而非自己定义的操作符重载函数了。
在例4中,我们以顶层函数的形式进行操作符重载,但是因为无法直接访问complex类中的私有成员,故而我们在类中增添了getimag、getreal、setimag和setreal函数以操作类中的私有成员变量,如此一来实现这些操作符重载函数看上去就有些复杂了,不是那么直观。除了此种方法以外,我们还可以将complex类中的私有成员real和imag声明为public属性,但是如此一来就有悖类的信息隐藏机制了。除了这两种方法外,我们是否还有其它方法解决这个问题呢?
有,还有一种方法。在前面章节中我们介绍过友元函数,如果我们将操作符重载函数这些顶层函数声明为类的友元函数,那么就可以直接访问类的私有成员变量了。
例5:
#include <iostream> using namespace std; class complex { public: complex(); complex(double a); complex(double a, double b); friend complex operator+(const complex & A, const complex & B); friend complex operator-(const complex & A, const complex & B); friend complex operator*(const complex & A, const complex & B); friend complex operator/(const complex & A, const complex & B); void display()const; private: double real; //复数的实部 double imag; //复数的虚部 }; complex::complex() { real = 0.0; imag = 0.0; } complex::complex(double a) { real = a; imag = 0.0; } complex::complex(double a, double b) { real = a; imag = b; } //打印复数 void complex::display()const { cout<<real<<" + "<<imag<<" i "; } //重载加法操作符 complex operator+(const complex & A, const complex &B) { complex C; C.real = A.real + B.real; C.imag = A.imag + B.imag; return C; } //重载减法操作符 complex operator-(const complex & A, const complex &B) { complex C; C.real = A.real - B.real; C.imag = A.imag - B.imag; return C; } //重载乘法操作符 complex operator*(const complex & A, const complex &B) { complex C; C.real = A.real * B.real - A.imag * B.imag; C.imag = A.imag * B.real + A.real * B.imag; return C; } //重载除法操作符 complex operator/(const complex & A, const complex & B) { complex C; double square = A.real * A.real + A.imag * A.imag; C.real = (A.real * B.real + A.imag * B.imag)/square; C.imag = (A.imag * B.real - A.real * B.imag)/square; return C; } int main() { complex c1(4.3, -5.8); complex c2(8.4, 6.7); complex c3; c3 = c1 + c2; cout<<"c1 + c2 = "; c3.display(); cout<<endl; c3 = c1 - c2; cout<<"c1 - c2 = "; c3.display(); cout<<endl; c3 = c1 * c2; cout<<"c1 * c2 = "; c3.display(); cout<<endl; c3 = c1 / c2; cout<<"c1 / c2 = "; c3.display(); cout<<endl; return 0; }
本例就是采用友元函数的形式进行操作符重载,如此实现既能继承操作符重载函数是顶层函数的优势,同时又能够使操作符重载函数实现起来更简单。