9.5.2 以成员函数形式重载运算符
成员函数形式的运算符声明和实现与成员函数类似,首先应当在类定义中声明该运算符,声明的具体形式如下所示。
返回类型operator运算符(参数列表);既可以在类定义的同时定义运算符函数使其称为inline型,也可以在类定义之外定义运算符函数,但要使用作用域限定符“:”,类外定义的基本格式如下所示。 返回类型类名:operator运算符(参数列表) { …… }
代码9.7 定义了复数类,并重载了其四则运算。
代码9.7 以成员函数形式重载运算符OperatorOverload1
<—————————————-文件名:complex.h————————————————> 01 #include<iostream> 02 using namespace std; 03 class complex//定义复数类complex 04 { 05 private: 06 double real,imag;//private成员,分别代表实部和虚部 07 public: 08 complex(double r=0. 0,double i=0.0)//构造函数,带默认参数值 09 { 10 real=r; 11 imag=i; 12 } 13 complex operator+(const complex&);//成员函数形式重载加 14 complex operator-(const complex&);//成员函数形式重载减 15 complex operator-();//成员函数形式重载一元(取反) 16 complex operator*(const complex&);//成员函数形式重载乘 17 complex operator/(const complex&);//成员函数形式重载除 18 complex&operator++();//成员函数形式重载前置++ 19 complex operator++(int);//成员函数形式重载后置++ 20 void disp()//成员函数,输出复数 21 { 22 cout<<real<<"+"<<"i*"<<imag<<endl; 23 } 24 }; <——————————————-文件名:complex.cpp——————————————> 25 #include"complex.h"//包含了类complex的定义 26 complex complex:operator+(const complex&CC)//加的实现 27 { 28 return complex(real+CC. real,imag+CC.imag); 29 } 30 complex complex:operator-(const complex&CC)//减的实现 31 { 32 return complex(real-CC. real,imag-CC.imag); 33 } 34 complex complex:operator-()//单目-,即取反的实现 35 { 36 return complex(-real,-imag); 37 } 38 complex complex:operator*(const complex&CC)//乘的实现 39 { 40 return complex(realCC. real-imagCC.imag,realCC.imag+imagCC.real); 41 } 42 complex complex:operator/(const complex&CC)//除的实现 43 { 44 return complex((realCC. real+imag+CC.imag)/(CC.realCC.real+CC.imag*CC.imag), 45 (imagCC. real-realCC.imag)/(CC.realCC.real+CC.imagCC.imag)); 46 } 47 complex&complex:operator++()//前置++的实现 48 { 49 cout<<"前置++"<<endl; 50 real+=1; 51 imag+=1; 52 return(*this); 53 } 54 complex complex:operator++(int)//后置++的实现,体会和前置++的区别 55 { 56 cout<<"后置++"<<endl; 57 complex ctemp=*this; 58 ++(*this); 59 return ctemp; 60 } <———————————-文件名:example907.cpp———————————————-> 61 #include"complex.h" 62 int main() 63 { 64 complex cx1(1. 0,2.0),cx2(3.0,4.0),cxRes; 65 cxRes=cx1-cx2;//相当于cx1.operator-(cx2) 66 cxRes. disp(); 67 cxRes=-cx1;//相当于cx1.operator-() 68 cxRes. disp(); 69 cxRes=cx1+cx2;//相当于cx1.operator+(cx2) 70 cxRes. disp(); 71 cxRes=cx1cx2;//相当于cx1.operator(cx2) 72 cxRes. disp(); 73 cxRes=cx1/cx2;//相当于cx1.operator/(cx2) 74 cxRes. disp(); 75 complex cx3(1. 0,1.0),cx4(5.0,5.0); 76 cxRes=++cx3;//相当于cx3.operator++() 77 cxRes. disp(); 78 cx3. disp(); 79 cxRes=cx4++;//相当于cx4.operator++(0) 80 cxRes. disp(); 81 cx4. disp(); 82 return 0; 83 }
输出结果如下所示。
-2+i*-2 -1+i*-2 4+i*6 -5+i*10 0.36+i*0.08 前置++ 2+i*2 2+i*2 后置++ 前置++ 5+i*5 6+i*6
【代码解析】代码13~19行,重载了复数类的四则运算、取反及自增操作,每个复数由实部和虚部组成,重载的+、-、*、/、取反(单目-)以及++符合复数的运算规则,在定义四则运算符和取反运算符时,采用了返回无名对象的方式,如下所示。
complex complex:operator+(const complex&CC) { return complex(real+CC.real,imag+CC.imag); }相比下列形式。 complex complex:operator+(const complex&CC) { complex c(real+CC.real,imag+CC.imag); return c; }
在两种方式中,似乎第2种更容易理解,可读性强,但第1种的效率要高于第2种。第2种定义方式做了3项工作,首先对象c被创建,同时完成初始化,然后,复制构造函数将c复制到保存返回值的外部存储单元中,最后,函数执行结束,c的析构函数被调用,c被撤销,其对应的非堆内存被释放。而对第1种定义方式而言,编译器直接在外部存储单元中创建了对象,并初始化,省去了复制和析构的开销。
需要强调的是,无论如何定义重载的运算符,也无法改变该运算符在C++中内置的结合性。例如,不能将单目取反操作放在cx1的后面,写成“cx1-”是不合法的。
代码9.7 中,对双目运算符+、-、*、/来说,编译器将左边对象解释为调用对象,将右边对象解释为传递给运算符的参数。例如cx1+cx2等价于cx1.operator+(cx2)。对单目运算的调用有两种形式:“对象+运算符”或者“运算符+对象”。在符合该运算符结合性顺序的前提下,编译器分别将两种形式解释为:“对象.operator运算符(0)”和“对象.operator运算符”,代码9.7中,分别重载了前置自增和后置自增运算符,两者的实现形式也与四则运算和取反运算有所不同。
前置自增的定义形式如下所示。
complex&complex:operator++() { cout<<"前置++"<<endl; real+=1; imag+=1; return(*this); }
返回自身引用(*this)使得“++对象”可以作为左值(能放在等号的左边),而且函数的实现符合“先增1,再引用”的原则。编译器将“++对象”解释为“对象.operator++()”。其他前置单目运算符与此类似。
后置自增的定义形式如下所示。
complex complex:operator++(int) { cout<<"后置++"<<endl; complex ctemp=*this; ++(*this); return ctemp; }
同前置自增不同的是,该函数以传值形式返回,这样,“对象++”不能放在等号的左边(不能对直接函数返回值区域赋值),也就不能成为左值,函数内部实现也符合“先引用,后增1”的原则。编译器将“对象++”解释为“对象.operator++(0)”,其他后置单目运算符与此类似。
说明
由此可以理解为什么“++++变量”是左值,而“++变量++”不是左值。