第8章运算符重载
第八章操作符重载
重载是C++多态性的体现之一。当定义新的数据类型之后,C++原有操作符提供的操作
在语义往往不能满足对新的数据类型的对象进行操作,因此必须对C++原有操作符的操作语
义进行扩充,这就是重载的应用需求背景。
8.1操作符重载概述
当在同一作用域内声明两个或多个相同的名字(即标识符)时,称该名字被重载。在同一作用域内的两个声明,如果声明的名字相同但是数据类型不同,则称这两个声明为重载声明。C++规定,只有函数声明可以被重载,对象声明或类型声明不允许重载。换言之,
C++的这一规定将重载严格限制在函数范畴。
当重载的函数被调用时,从诸个可调用的重载函数( viable fu nctio ns )中究竟调用
那一个函数则由调用时实参的类型与函数声明时形参的类型相比较结果的一致性决定。这个选择与决定的过程称为重载解析。在C++中,根据函数的定义者是谁可以将函数分为两类。
一类是由程序员定义的函数,它们往往被称为用户自定义函数,另一类则是系统提供的函
数。就系统提供的函数而言,根据它们的调用方式,又可以进一步分为两类。一类是与用
户自定义函数调用方式相同的系统函数,它们往往称为库函数或类库中的成员函数;另一
类则沿用自然语言和数学语言的使用习惯,在各类表达式中完成相应的运算,它们往往称为操作符或运算符,但实际上是系统的预定义函数或操作符函数。例如对整型对象x、y,x+y 实际表示对预定义函数’+'的调用。x和y是预定义函数’+'的参数,但一般习惯上称为
‘ + '的左操作数和右操作数。由于操作符实际上也是函数,不同的只在于操作符是系统的
预定义函数,因此操作符和用户自定义函数一样也可以重载。
以加法操作‘ +'为例,C++提供的‘ +'操作如果不考虑类库支持,则只能进行整数或实数的加法运算,若考虑类库支持则能够进行一般复数的运算。如果用复数来表示电路中的电流和电压,根据电路理论,只有电流和电流才能进行相加减的运算;同理,只有电压
和电压才能进行相加减的运算。因此,为了将复数用于电路计算,可以象下面的例子那样
设计一个用于电路计算的复数类,并且对加法操作的语义进行扩充。例8-1设计一个能够用于电路计算的复数类,并重载加法操作。
#i nclude "iostream.h"
struct complex
{
complex(double re=0.0,double im=0.0,char ch1='U');
complex operator+(complex & c);〃声明重载加法操作
void show();
private:
double real,imag;
char ch;
};
complex::complex(double re,double im,char ch1 )
{
real=re;imag=im;ch=ch1;
cout<<"c on structor is called!"< } complex complex::operator+(complex& c) { // 定义重载加法操作,扩充加法操作的语义 double x,y; if(ch==c.ch){ x=real+c.real;y=imag+c.imag; return complex(x,y,ch); } else cout<<"can't execute the plus opetating!"< return complex(0,0,'U'); } void complex::show() { cout< if(imag>=0) cout< else cout< } void main(void) { complex c1(0,0,'I'),c2(1.5,2.3,'I'),c3(2.7,-9.2,'I'); c1=c2+c3;// 可以用c1=c2.operator+(c3) 来替换c1.show(); } 程序的运行结果为; constructor is called! constructor is called! constructor is called! constructor is called! //return 语句中通过构造函数实现类型转换 I=4.2-6.9i 程序在complex类中声明了一个字符数据成员ch,并且对加法运算进行了重载。C++ 规定,当进行操作符重载时,要在操作符前面冠以关键字operator 。因此在对‘ +'进行重 载时就用了operator+ 作为加法操作的函数调用符。重载后的加法操作要求参与相加的两个对象的 ch 成员值相同时才允许进行加法操作,否则认为出错。编译器将表达式c2+c3 的理 解成为c2.operator+(c3) 。事实上,用c2.operator+(c3) 代替c2+c3 是完全可以的。成员函数show 输出对象究竟是电流还是电压的信息,并且按照a+bi 或a-bi 的形式输出复数值。对程序的完全理解可以在学完8.2 节以后再回头阅读分析本例。 在C++中,除了‘ . ’、’ .* '、’::’、’ ?:'这四种操作符不允许重载,在表8-1中 列出了C++中可被重载的操作符。可重载的操作符按照操作数的个数又可以分为只有一个操作数的单目操作符,有两个操作数的双目操作符,以及另外的赋值操作符=、下标操作符[] 、和类成员访问操作符->。在表达式a[b] 中,下标操作符函数operator[] 的右操作数出现在[] 的中间,与传统操作符+、-、*、/操作数分居两侧有所不同。操作符与函数调用符之间的关系如表 8-2所示,其中@表示某个操作符。单目操作符还有前缀式与后缀式之分。 8-2 对于操作符重载,有如下需要注意的事项: 1.操作符重载不改变操作符原有的优先级和结合性。例如对于算术运算,仍然按照先乘除,后加减的顺序进行运算。又例如,对于辗转赋值:x=y=z,结合性仍然是右结合;即先 进行y=z赋值操作,再进行对x的赋值操作。 2.操作符重载时不能改变它的操作数的个数,不能有带缺省值的参数。也不能改变操作符 使用的语法规则。 3.操作符重载是对操作符原有语义的扩充,而不是完全改变操作符的基本语义。例如:从 技术上讲,完全可以将‘ +'操作符重载之后做减法运算,但是这样做毫无意义。又例 女口,重载++或--操作符时,仍然应该保持前缀++或--先对操作数自增或自减,然后用自增或自减后操作数的值参与运算;同样,也应该保持后缀++或--先用自增或自减之前操 作数的值参与运算,然后再对操作数做自增或自减运算这样一些基本操作语义不变。4.操作符还可以重载为一般函数的形式。即:既非重载为类的成员函数,也不是重载为类 的友员函数。此时操作符函数具有全局作用域,并且至少应该有一个类类型的参数。 由于每个操作符的操作数是C++语言预先规定好了的,因此进行操作符重载时,参数的 个数仍然应该C++语言预先规定设置。操作符重载可以在类中进行,也可以在类外进行。在 类外重载的操作符只能访问类中的公有成员。为了使操作符能够访问类中的私有成员,一 般将操作符重载为类的成员函数或类的友员函数。如果将操作符重载为类的非静态成员函 数,则系统会向其提供this指针,此时操作符函数需要的参数比C++语言预先规定的参数 要少一个。因此可以得出如下关于操作符重载时形参个数的规定: 1.对单目操作符,当它重载为类的友员函数时,只能声明一个形参;当它重载为类的成员 函数时不能再显示声明形参,所需参数由this指针提供。 2.对双目操作符,当它重载为类的友员函数时,只能声明两个形参;当它重载为类的成员 函数时,只能声明一个形参,该形参是操作符的右操作数,操作符的左操作则由this 指针提供。 3.如果在类外进行操作符重载,声明形参的个数与将操作符重载为类的友员函数时相同。 8.2 操作符重载为类的成员函数 操作符重载为类的成员函数声明的一般形式是: T operator 操作符(形参表); 其中,T是操作符函数的返回值的数据类型。operator是关键字。“operator操作符” 称为函数调用符(function-call notation )。形参表中的形参要按照上面规定给出。 操作符重载为类的成员函数定义的一般形式是: T 类名::operator 操作符(形参表) { , // 函数体 } 现以复数为例讨论操作符的重载问题。复数有代数式(Algebra )与极坐标式(Polar coordinates )之分。复数的代数式适合进行加减运算,而复数的极坐标式则适合进行乘除运算。下面设计一个允许复数的代数式与极坐标式共存的复数类,它允许代数式的复数与极坐标式的复数进行不加转换的运算。每一个复数对象有一个标志成员flag 。flag 的值为’A'表示该复数是代数式的复数,若flag的值为’P'表示该复数是极坐标式的复数。 用户在创建了复数对象之后就无须关心它的表现形式而自由的进行四则运算或比较操作。形式转换等操作在复数类的内部自动完成。该复数类规定:加法操作结果的表现形式(即代数式或极坐标式)与左操作数相同;赋值操作后由右操作数的表现形式决定左操作数的 表现形式。显示输出函数show在输出极坐标形式的复数时分别输出其模(norm)和幅角 (angle ),并且幅角在计算时以弧度形式出现,输出时以度的形式出现。数据成员real_norm 在代数形式下存放复数的实部,在极坐标形式下存放复数的模;数据成员imag_angle 在代 数形式下存放复数的虚部,在极坐标形式下存放复数的弧度形式的幅角值。 例8-2 能够进行代数式的复数与极坐标式的复数之间的自由四则运算和比较操作的复数类设计及其应用举例。 #include "iostream.h" #include "math.h" struct complex { complex(double re=0.0,double im=0.0,char flag1='A' ); // 数 complex(complex& rc);// 拷贝构造函数 complex& operator=(complex& c);// 重载赋值操作 complex operator+(complex& c); // 重载加法操作 complex operator*(complex& c); // 重载乘法操作 int operator==(complex& c); // void show();// 显示输出函数private: complex polar_to_algebra();// complex algebra_to_polar();// double real_norm;// 实部或模 double imag_angle;// 虚部或幅角 char flag;// ' A'表示代数式,' }; 带缺省参数的构造函 重载相等比较操作 极坐标式到代数式转换函数代数式到极坐标式转换函数 P'表示极坐标式 complex::complex(double re,double im,char flag1 ) { // 构造函数,缺省时为代数式的复数 real_norm=re;imag_angle=im;flag=flag1; } complex::complex(complex& rc) { // 拷贝构造函数 real_norm=rc.real_norm;imag_angle=rc.imag_angle; flag=rc.flag; // 被初始化复 数的表现形式与初值复数对象一致 } complex& complex::operator=(complex& c) { real_norm=c.real_norm;imag_angle=c.imag_angle; flag=c.flag; // 被赋值的复数的表现形式与右操作 数一致 return *this; } complex complex::operator+(complex& c) { complex t; if(flag=='A' && c.flag=='A'){ // 两者皆为代数式 t.real_norm=real_norm+c.real_norm; t.imag_angle=imag_angle+c.imag_angle; } if(flag=='A' && c.flag=='P'){ //this 所指复数为代数式 t=c.polar_to_algebra(); // 引用复数 由极坐标式转换为代数式 t.real_norm=real_norm+t.real_norm; t.imag_angle=imag_angle+t.imag_angle; } if(flag=='P' && c.flag=='A'){ //this 所指复数为极坐标式 t=this->polar_to_algebra();// 由极坐标式转换为代数式并赋值 t.real_norm=c.real_norm+t.real_norm; t.imag_angle=c.imag_angle+t.imag_angle; t=t.algebra_to_polar();// 由代数式转换为极坐标式,与左操作数形式一致 t.real_norm=t1.real_norm+t.real_norm; t.imag_angle=t1.imag_angle+t.imag_angle; t=t.algebra_to_polar();// 由代数式转换为极坐标式,与左操作数形式一致 } return t; complex complex::operator*(complex& c) { complex t; if(flag=='A' && c.flag=='A'){ // 两者皆为代数式 complex t1; t1=c.algebra_to_polar();// 由代数式转换为极坐标式 t=this->algebra_to_polar();// 由代 数式转换为极坐标式 t.real_norm=t.real_norm*t1.real_norm;// 模相乘 t.imag_angle=t.imag_angle+t1.imag_angle;// 幅角相加 t=t.polar_to_algebra();// 由极坐 标式转换为代数式,与左操作数形式一致 } if(flag=='A' && c.flag=='P'){ } if(flag=='P' && c.flag=='P'){ // complex t1; t1=c.polar_to_algebra();// t=this->polar_to_algebra();// 两者皆为极坐标式 由极坐标式转换为代数式 由极坐标式转换为代数式