第8章运算符重载

合集下载
相关主题
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

第八章操作符重载
重载是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!"<<e ndl;
}
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!"<<endl;
return complex(0,0,'U');
}
void complex::show()
{
cout<<ch<<"=";
if(imag>=0)
cout<<real<<"+"<<imag<<"i"<<endl;
else
cout<<real<<imag<<"i"<<endl;
}
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();//
两者皆为极坐标式 由极坐标式转换为代数式
由极坐标式转换为代数式
t=this->algebra_to_polar(); t.real_norm=t.real_norm*c.real_norm;
t.imag_angle=t.imag_angle+c.imag_angle;
t=t.polar_to_algebra();// 由极坐标式转换为代数式,与左操作数形式一}
if(flag=='P' && c.flag=='A'){
t=c.algebra_to_polar();
t.real_norm=real_norm*t.real_norm; t.imag_angle=imag_angle+t.imag_angle;
}
if(flag=='P' && c.flag=='P'){
t.real_norm=real_norm*c.real_norm; t.imag_angle=imag_angle+c.imag_angle;
t.flag=flag;〃t.flag 缺省为’A',现必须赋成’P'
}
return t;
}
int complex::operator==(complex& c)
{ // 算法:参与比较的两个复数都转换为代数式,并分别赋给t,t1 ;
// 然后对t,t1 进行比较。

相同返回1,不同返回0。

complex t,t1;
if(flag=='A') t=(*this);
else t=this->polar_to_algebra();
if(c.flag=='A') t1=c;
else t1=c.polar_to_algebra();
if(t.real_norm==t1.real_norm && t.imag_angle==t1.imag_angle)
return 1;
else
return 0;
}
void complex::show()
{
if(flag=='A'){
cout<<flag<<"="; if(imag_angle>=0) cout<<real_norm<<"+"<<imag_angle<<"i"<<endl;
else cout<<real_norm<<imag_angle<<"i"<<endl;
} else
cout<<"norm="<<real_norm<<" angle="<<imag_angle/3.14159*180<<endl; }
complex complex::polar_to_algebra()// 由极坐标式转换为代数式的转换函数{
complex tmp; tmp.imag_angle=real_norm*sin(imag_angle);
tmp.real_norm=real_norm*cos(imag_angle);
return tmp;
}
complex complex::algebra_to_polar()// 由代数式转换为极坐标式的转换函数{
complex tmp; tmp.real_norm=sqrt(imag_angle*imag_angle+real_norm*real_norm);
if(real_norm!=0)
tmp.imag_angle=atan(imag_angle/real_norm);
else
tmp.imag_angle=3.14159/2;
tmp.flag='P'; return tmp;
}
void main(void)
{
complex c1(0,0,'A'),c2(1.5,2.3,'A'),c3(2.7,-9.2,'A');
c1=c2+c3; // 也可以写成c1=c2.operator+(c3)
c1.show();
c1=c2*c3; // 也可以写成c1=c2.operator*(c3)
c1.show();
complex c4(0,0,'P'),c5(10,3.14159/6,'P'),c6(20,3.14159/6,'P'); c4=c5.operator+(c6);// 也可以写成c4=c5+c6 c4.show();
c4=c5.operator*(c6);// 也可以写成c4=c5*c6 c4.show();
complex c7(0,0,'P'),c8(1.5,2.3,'A'),c9(20,3.14159/6,'P');
c8=c7+c8; // 将c8 由代数式转换为极坐标式
c7=c8+c9;
c7.show();
c7=c8*c9; c7.show();
complex c10(0,0,'P'),c11(20,3.14159/6,'P'),c12(1.5,2.3,'A'); c10=c11+c12; c10.show(); c10=c11*c12; c10.show(); int x=(c7==c10);
cout<<"c7==c10? "<<x<<endl;
//c1=c2+c3 的运算结果 //c1=c2*c3 的运算结果
//c4=c5.operator+(c6) 的运算结果 //c4=c5.operator*(c6) 的运算结果 //c7=c8+c9 的运算结果 //c7=c8*c9 的运算结果 //c10=c11+c12 的运算结果
//c10=c11*c12 的运算结果 // c7==c10 的比较结果
本例中对复数的加、乘和相等比较操作进行了重载。

重载加法操
作时算法的基本思想 是将复数转换成为代数式之后再进行加法运算,并且返回时复数的表现形式应该按照要求 与左操作数的表现形式一致。

重载乘法操作时算法的基本思想则是先将复数转换为极坐标 式,然后进行乘法操作,当然返回时复数的表现形式也应该与左操作数一样。

值得注意的 是:在进行诸如 c2+c3 的加法运算时,调用的是程序中重载的加法操作,而在进行诸如;
t.real_norm=real_norm+c.real_norm;
的运算时,调用的却是系统提供的两个双精度数之间的加法操作。

究竟调用那一个侯选函 数,完全要看操作符左、右操作数的数据类型。

在重载加法和乘法操作时,程序中先用:
complex t;
声明一个复数类的局部对象。

在返回时,用:
return t;
返回运算后的结果。

这样做的目的是为了严格遵守 C++关于加法和乘法操作中不修改左、 右
操作数的语法规定。

另外,在赋值操作 operator=(complex& c) 函数中, real_norm 和 imag_angle 都是 this 指针所指的当前对象,也就是左操作数。

例如:在 c1=c2+c3 中,赋值操作的左操作数就是 cl 。

因此,return
*this; 中,返回的就是 cl 的值。

8.3 操作符重载为友员函数 操作符重载为类的友员函数 声明的一般形式是: friend T operator 操
作符 ( 形参表 ) ; 其中, friend 是关键字,它说明该函数是类的友员函数,因此该函数对类成员具有一 切存取权限。

T 是操作符函数的返回值的数据类型。

operator 是关键字。

形参表中的形参应
该按照C++语言预先规定的数目给出。

操作符重载为类的友员函数 定义的一般形式是:
}
程序的运行结果为: A=4.2-6.9i
A=25.21-7.59i norm=30 angle=30 norm=200 angle=60 norm=22.4834 angle=33.1663 norm=54.9181 angle=86.8887 norm=22.4834 angle=33.1663 norm=54.9181 angle=86.8887 c7==c10? 1
T operator 操作符 ( 形参表 )
{
, // 函数体
}
除了以 operator 操作符作为函数调用符之外,定义的形式与一般普通函数相同。

关键 字 friend 只能出现在友员函数的声明语句中,在友员函数的定义性声明中不应该出现。

下面以IC 卡为例设计一个IC_Card 类。

IC_Card 类共有4个数据成员,分别是持卡标识 持卡人的账号 accounts 、持卡人的密码 password 、以及以分为最小整数单位的持卡人的卡 内存款余额money 。

以分为最小整数单位是为了顾及
IC 卡位编址特性和尽量简化卡内存储
操作。

同时对充值(加)操作、减值(减)操作进行了重载。

由于类中重载的充值操作只 允许进行整数充值,所以在类外又对充值操作进行了重载,它允许进行小数操作。

四个 操作分别对持卡标识、持卡人的账号、持卡人的密码、以及卡内存款余额进行修改设置。

例 8-3 设计一个 IC 类,并且在类中和类外进行操作符的重载。

#include "iostream.h" class
IC_Card public:
IC_Card();// 缺省构造函数
IC_Card(char *id1,char * ac,char * pa,int m);//
IC_Card::IC_Card(char *id1,char * ac,char * pa,int m)
{
strcpy(id,id1);strcpy(accounts,ac); strcpy(password,pa);money=100*m;
}
IC_Card& IC_Card::operator=(IC_Card& card)
{
strcpy(id,card.id);strcpy(accounts,card.accounts);
id 、 set 有参构造函数
~IC_Card(){}// 析构函数
IC_Card& operator=(IC_Card& card);// friend int operator+(IC_Card& card,int m);// friend int operator-(IC_Card&
card,int m);// void show();// 显示输出函重载赋值操作
重载充值操作,只允许整数 消费减值操作
void set_accounts(char *ac);// 设置持卡人的账号 void set_password(char *pa);// 设置持卡人的密码
void set_money(int sm){money=sm;}// char id[9];// 持卡标识 void strcpy(char *s,char
*t);//IC_Card private:
char accounts[9];// 持卡人的账号 char password[5];//
持卡人的密码
int money;// 卡内存款余额 };
设置存款余额
类的字符串拷贝函数
void set_id(IC_Card& card,char *pid);// void operator+(IC_Card& card,double m);
在类外设置持卡标识
在类外重载充值操作,允许小数
IC_Card::IC_Card()
{
strcpy(id,"88888888");strcpy(accounts,"00000000");
strcpy(password,"8888");money=0;
strcpy(password,card.password);money=card.money; return *this;
}
void IC_Card::set_accounts(char *ac)
{ strcpy(accounts,ac);
}
void IC_Card::set_password(char *pa)
{ strcpy(password,pa);
}
void IC_Card::strcpy(char *s,char *t)
{
int i=0;
while(s[i] = t[i])
i++;
}
void IC_Card::show()
{
cout<<"id="<<id<<" accounts="<<accounts \
<<" password="<<password<<" money="<<(double)money/100<<endl; }
int operator+(IC_Card& card,int m)
{ card.money=card.money+100*m; return card.money;
}
int operator-(IC_Card& card,int m)
{ card.money=card.money-100*m; return card.money;
}
void set_id(IC_Card& card,char *pid)
{
card.strcpy(card.id,pid);
void operator+(IC_Card& card,double m)
{
int m1=0;
m1=card+m1;
m1=m1+(int)100*m; card.set_money(m1);
}
void main(void)
{
IC_Card card1,card2("10001234","cs015678","1128",258);
card1.show();
card2.show();
set_id(card1,"10009876");
card1.set_accounts("cs016666"); card1.set_password("3584");
card1+123;
card1+0.23;
card2-58;
card1.show();
card2.show();
}
程序的运行结果为:
id=88888888 accounts=00000000 password=8888 money=0
id=10001234 accounts=cs015678 password=1128 money=258
id=10009876 accounts=cs016666 password=3584 money=123.23 id=10001234 accounts=cs015678 password=1128 money=200 friend 关键字仅仅出现在友员函数的声明中。

由于操作符被重载为类的友员函数,因此加、减操作符都有两个形参,且左操作数是关于IC_Card 类对象的引用,右操作数则是
整型形参。

从money+1OO*m和money-100*m可以看出,IC_Card类仅仅在其内部才是以分为最小整数单位的,对外仍然按照习惯的元、角、分形式进行处理,从而将以分为最小整数单位的特性封装于
IC_Card 类的内部。

在类外重载的充值操作则允许进行小数充值,其右操作数是double类型的形参;在它的内部,m仁card+m1;中的加法操作调用的是:
int operator+(IC_Card& card,int m);
函数,在operator+(IC_Card& card,int m) 内部card.money=card.money+100*m; 语句中的加法操作则是调用系统提供的加法操作。

由于类外重载的充值操作不能访问IC_Card 类的
私有数据成员money,因此在计算出新的余额之后,通过调用set_money操作来完成余额值的更新。

本例中,消费减值只能以元为单位进行,读者可以仿照类外重载充值操作自行设计一个类外重载的减值操作。

当然,还有其他相应的办法, 读者不妨试试。

同时,象11+card1 的操作在本例中也没有得到支持,作为练习,读者可以自行编制重载左操作数为数值型数据的加法操作。

8.4 增量/ 减量操作符的重载
在进行增量操作符++和减量操作符-- 的重载时,要注意++和-- 有前缀式与后缀式之分。

根据表8-2 中关于前缀单目操作符和后缀单目操作符重载为类的成员函数和重载为类的友元函数的规定以及前面讨论的关于单目操作符参数个数的规定。

当前缀++和前缀-- 重载为类的成员函数时,++obj 和--obj 编译器理解的形式为:
obj.operator++() 和obj.operator--()
函数原型为:T operator++() ;和T operator--() ;其中,T 为返回类型。

当前缀++和前缀-- 重载为类的友员函数时,++obj 和--obj 编译器理解的形式为:
operator++(obj) 和operator--(obj)
函数原型为:friend T operator++(U obj) ;和friend T operator--(U obj) ;
其中,T 为返回类型,U 为操作数obj 的数据类型。

函数定义时不要用friend 关键字。

同理,当后缀++和后缀-- 重载为类的成员函数时,obj++ 和obj-- 编译器理解的形式为:
obj.operator++(0) 和obj.operator--(0)
函数原型为:T operator++(int ) ;和T operator--(int ) ;其中,T 为返回类型。

并且,当后缀++和后缀-- 重载为类的友员函数时,obj++ 和obj-- 编译器理解的形式为:operator++(obj,0) 和operator--(obj,0)
函数原型为:friend T operator++(U obj,int ) ;和friend T operator--(U obj ,int )
其中,T 为返回类型,U 为操作数obj 的数据类型。

函数定义时不要用friend 关键字。

仍然以例8-3 的IC_Card 为例,扩充增量和减量操作符的重载。

将前缀++和前缀--
载为类的成员函数;将后缀++和后缀-- 重载为类的友元函数。

例8-4 扩充IC_Card 类,完成前缀++和前缀-- ,以及后缀++ 和后缀-- 的重载。

1.在IC_Card 类的声明中扩充下面声明:
IC_Card operator++();
IC_Card operator--();
friend IC_Card operator++(IC_Card& card,int);
friend IC_Card operator--(IC_Card& card,int);
2.在IC_Card 类的实现中扩充下面函数的实现:
IC_Card IC_Card::operator++()
{
++money;
return *this;
}
IC_Card IC_Card::operator--()
{
--money;
return *this;
}
IC_Card operator++(IC_Card& card,int)
{
IC_Card card1=card;
card.money++; // 与card1.money=card.money++; 等价
return card1;
}
IC_Card operator--(IC_Card& card,int)
{
IC_Card card1=card; card.money--;// 与card1.money=card.money--; 等价return card1;
}
3.相应的mian 函数如下:
void main(void)
{
IC_Card card1,card2("10001234","cs015678","1128",258);
card1.show();
card2.show();
set_id(card1,"10009876");
card1.set_accounts("cs016666");
card1.set_password("3584");
card1+123;
card1+0.23;
card2-58;
card1.show();
card2.show();
IC_Card card3,card4;
card3=++card1;
cout<<"after card3=++card1, card1 is:"<<endl;
card1.show();
cout<<"after card3=++card1, card3 is:"<<endl;
card3.show(); card4=card1++; cout<<"after card4=card1++, card1 is:"<<endl;
card1.show();
cout<<"after card4=card1++, card4 is:"<<endl;
card4.show();
}
程序的运行结果为:
id=88888888 accounts=00000000 password=8888 money=0
id=10001234 accounts=cs015678 password=1128 money=258
id=10009876 accounts=cs016666 password=3584 money=123.23 id=10001234 accounts=cs015678 password=1128 money=200 after card3=++card1, card1 is:
id=10009876 accounts=cs016666 password=3584 money=123.24 after card3=++card1, card3 is:
id=10009876 accounts=cs016666 password=3584 money=123.24 after card4=card1++, card1 is: id=10009876 accounts=cs016666 password=3584 money=123.25 after card4=card1++, card4 is: id=10009876 accounts=cs016666 password=3584 money=123.24
显然,card3=++card1 中的前缀++使card1 和card3 的money 成员的值同为123.24 ;
而card4=card1++ 中的后缀++使card1 的money 成员的值为123.25 ,而card4 的money 成员的值为123.24,满足后缀++的操作语义。

8.5下标操作符的重载
下标操作符[]是一个双目操作符。

它的左操作数是数组名,右操作数是下标。

下标操
作符[]只能被重载为类的非静态成员函数。

因此只能为下标操作符显式声明一个形参,该形参为下标操作符的右操作数,左操作数则由this指针提供。

设在类T中重载下标操作符,则下标操作符重载声明的一般形式为:
T1 operator[](U );
其中,T1是返回值的数据类型,U是形参的数据类型,operator]]是函数调用符。

下标操作符重载定义的一般形式为:
T1 T::operator[](U )
{
...//函数体
}
对下标操作符进行了重载声明和定义之后,就可以采用下面的形式来调用它:
a[b] 或: a.operator[](b)
其中,a是左操作数,相当于数组名。

此处所谓的相当是指:数组名是一个常量,而此
处的a却不一定是常量;但是a又可以象数组名一样使用。

b是下标,它可以是任意类型。

如整型、字符型、甚至是某个类类型。

例8-5利用下标操作符重载设计一个能够判断下标越界的双精度型数组并对其进行操作。

#i nclude "iostream.h"
#i nclude "stdlib.h"
class array
{
public:
array(i nt size);
~array();
double& operator[]( int i);
private:
double *a; int up_bo und;
};
array::array(i nt size)
{
up_bo un d=size;
a=new double[size];
}
array::~array()
{
delete []a;
}
double& array::operator[](i nt i)
{
if(i<0 ||i>=up_bound){
cout<<"subscript overflow!"<<endl;
exit(1);
}
return a[i];
}
void main(void)
{
array b(10);
b[0]=12.34;
b[3]=b[2]=b[1]=b[0];
b[8]=567.89;
cout<<b[0]<<" "<<b[3]<<" "<<b[8]<<endl;
b[10]=256.78;
} 程序运行结果为:
12.34 12.34 567.89
subscript overflow!
程序中用到了new[] 和delete[] 操作符,a=new double[size] ;语句将先执行new[] 操作,创建一个有size个元素的动态数组,并将该数组的起始地址赋给指针a。

析构函数
中的delete []a; 语句的作用是清除(即回收)由new[] 操作创建的动态数组。

详细内容请参阅第九章中的“ 9.10.2 new[] 操作和delete[] 操作”。

array b(10); 语句创建了一个array 类型的对象,指针成员 b.a 指向有10 个元素的double 型数组。

b[0]=12.34; 语句的执行过程是先计算b[0] ,调用重载过的下标操作符函数。

此时实参0 传递给形参i ,然后判断下标是否越界,如果没有越界,执行return a[i]; 而a[i] 显然是指针成员b.a 所指数组中打头的元素a[0] ,由于返回类型是double& ,因此计算b[0] 的最终结果是得到关于a[0] 的一个引用。

因此b[0]=12.34 就相当于a[0]=12.34 a[0] 是表达式b[0] 调用重载的下标操作符函数所得到的计算结果这句话是理解下标操作符重载的关键。

当执行b[10]=256.78; 语句时,由于下标越界而程序输出提示之后而终止执行。

例8-6 通过有上下文语义的下标操作符重载,实现对数据文件象对数组一样的操作。

#include
<fcntl.h>
#include <io.h>
#include <stdio.h> #include "iostream.h"
class FileRef {
private:
class File &f; //class File 兼做前向引用声明, f 是基类型为File 的引用
char buf[1];
unsigned long ix;
public:
FileRef(File&ff,unsigned long i):f(ff),ix(i){}// 构造函数
FileRef& operator=(char c); // 赋值操作符重载
operator char();// 转换函数
};
class File{
friend class FileRef;// 将FileRef 类声明为File 类的友元类。

public:
File(const char *name){
fd=_open(name,_O_RDWR|_O_CREAT,0664);//File 类的构造函数
}
~File(){_close(fd);} //File 类的析构函数
FileRef operator[](unsigned long ix){ // 下标操作符重载
return FileRef(*this,ix);
}
private:
int fd;
};
FileRef& FileRef::operator=(char c){ // 赋值操作符重载
_lseek(f.fd,ix,0);_write(f.fd,&c,1);
return *this;
}
FileRef::operator char(){ // 转换函数
_lseek(f.fd,ix,0);_read(f.fd,buf,1);
return buf[0];
}
int main()
{
File foo("abc");// 调用File(const char *name), File.fd 为文件句柄foo[5]='5';
// 先调用operator[](unsigned long ix) ,
// 再调用FileRef (File&ff,unsigned long i), 再调用operator=(char c)
foo[10]='1';
char c=foo[5];
// 先调用operator[](unsigned long ix) ,
// 再调用FileRef (File&ff,unsigned long i), 再调用operator char() 转换函数
cout<<"c="<<c<<endl;
return 0;
}
运行结果:
c=5
执行File foo("abc"); 语句将调用File 类的构造函数,创建File 类型的对象foo 。

从File 类的数据结构看,对象foo 有一个整型数据成员fd 。

构造函数的运行结果是创建一个名字为abc 的数据文件,并用foo.fd 作为数据文件abc 的句柄。

接下来执行foo[5]='5'; 语句,首先要计算foo[5] ,这将调用File 类中重载的下标操作。

该函数简单的执行return FileRef(*this,ix); 语句,此时*this 即foo ,而ix 的值为
5,它们作为实参引起FileRef 类构造函数的调用。

FileRef 类构造函数的调用将创建
FileRef类的对象(匿名对象),并使该对象的对象成员f引用foo,该对象的数据成员ix
的值为5。

该匿名对象作为foo[5]的计算结果值。

接着进行赋值操作,由于赋值号左边是
FileRef类型的对象,此时将调用FileRef类重载的赋值操作,而字符'5'作为实参传递给
形参c。

在赋值操作内部,」seek(f.fd,ix,O);_ 语句中f.fd 现在是数据文件abc的文件
句柄,ix的值是5,」seek函数是定位函数,它会使文件指针定位到数据文件abc的第5 个字符所在位置。

而_write(f.fd,&c,1); 语句则将形参c的值'5'写到数据文件abc的第5
个字符所在位置。

所以foo[5]='5'; 语句表面看似向数组元素赋值,实际上最终导致'5'写入到数据文件abc的第5个字符所在位置。

foo[10]='1'; 语句可以类似解释。

执行char c=foo[5]; 语句时情况又有所变化。

foo[5]是出现在赋值号的右边。

foo[5]
导致调用File类中重载的下标操作,以及该调用又会导致FileRef类构造函数的调用,最
终产生一个FileRef类的匿名对象,该匿名对象的对象成员f仍然引用foo,且foo.fd (也
就是f.fd )是数据文件abc的文件句柄,该匿名对象的数据成员ix的值也同样为5。

但是
由于现在是希望将一个FileRef类的匿名对象赋给一个字符变量c,这将导致FileRef类的
转换函数operator char() 被调用。

一个T类中的转换函数operator char() 的作用就将T类型的对象转换成为char类型
的对象,并且该函数在需要一个char类型的对象,但实际提供的是一个T类型的对象的时
候由系统自动调用。

因此,FileRef类的转换函数operator char() 的作用就是将FileRef 类型的对象转换成为char类型的对象,并且该函数在需要一个char类型的对象,但实际
提供的是一个FileRef类型的对象的时候由系统自动调用。

在c=foo[5];中,执行完foo[5]
后产生一个FileRef类的匿名对象,而赋值操作需要一个char类型的对象,因此,此时FileRef 类的转换函数operator char() 被系统自动调用。

再看转换函数内部,执行
_lseek(f.fd,ix,0); 语句的作用是定位到数据文件abc的第5个字符所在位置,而
_read(f.fd,buf,1); 语句则是该位置读一个字符,并且将该度入的字符存放到buf数组中,而return buf[0]则最终将读入的字符返回,并作为c=foo[5]中赋值号的右操作数。

这样,
输出结果为c=5就不难理解了。

8.6函数调用操作符的重载
重载的函数调用操作符是类的一个非静态成员函数,它是一个双目操作符,左操作数由this指针提供,右操作数是形参表,形参表中可以有一个或多个参数,也可以是空。

设在类T中重载函数调用操作符,则函数调用操作符重载声明的一般形式为:
T1 operator()( 形参表);
其中,T1是函数调用操作符函数返回值的数据类型。

函数调用操作符重载定义的一般形式为:
T1 T::operator()( 形参表)
{
…〃函数体
}
对函数调用操作符进行了重载声明和定义之后,就可以采用下面的形式来调用它:
a.(实参表)或: a.operator()( 实参表)
下面以计算三角形面积为例,说明函数调用操作符的重载。

例8-7重载函数调用操作符实现计算三角形面积的抽象。

#in clude "iostream.h"
#in clude "math.h"
class FUN
{。

相关文档
最新文档