1
第五章 多态性
2
5.1 编译时的多态性与运行时的多态性
多态性是指用 同一个名字 定义不同的函数,这些函数 执行不同 但
又类似的 操作 。
联编的概念,
一个源程序经过编译、连接、成为可执行文件的过程是把可执行
代码联编(或称装配)在一起的过程。
静态联编(前期联编)
静态联编要求在程序编译时就知道调用哪个函数,就决定如何
实现某一动作。
动态联编(后期联编、滞后联编)
一直要到程序运行时才能确定调用哪个函数。系统在运行时才动
态完成的联编。
静态联编支持的多态性称为编译时多态性,也称静态多态性。在
C++中,编译时多态性是通过 函数重载 和 运算符重载 实现的。
动态联编支持的多态性称为运行时多态性,也称动态多态性。在
C++中,运行
时多态性是通过 继承 和 虚函数 来实现的。
3
5.2 函数重载
编译时多态性可以通过函数重载来实现。函数重载的意义在于它能
用同一个名字访问一组相关的函数,也就是说,能使用户为某一
类操作取一个通用的名字,而由编译程序来选择具体由哪个函数
来执行,因而有助于解决程序的复杂性问题。 普通成员函数和构
造函数都可以重载
例 5.1 基类和派生类中函数重载的例子
#include <iostream.h>
class point{
int x,y;
public,
point(int a,int b){x=a;y=b;}
float area(){return 0.0;} //基类中的 area函数
};
class circle:public point{
int radius;
public,
circle(int x,int y,int rad):point(x,y){radius=rad;}
float area(){return 3,1416*radius*radius;}//派生类中的 area函数
};
Void main()
{ point p(20,20);
circle c(8,8,30);
cout<<p.area()<<endl; //执行基类 point中的 area()的函数
cout<<c.area()<<endl; //执行派生类 circle中的 area()函数
cout<<c.point::area()<<endl;//执行基类 point中的 area()的函数
}
程序运行结果为,
0
2827.439941
0
4
说明,
在基类和派生类中的函数重载有两种情况,
1.参数有所差别的重载,这种重载函数的定义和调用方法在前面章
节已进行了介绍;在编译时根据参数不同来区分函数重载。
2.函数所带的参数完全相同,只是它们属于不同的类。这时要人工
干预,以下两种方法可以使编译时区别这类函数,
( 1)使用对象名加以区分。例如,p.area()和 c.erea()分别
调用类 point的 area()函数和类 circle的 area()函数。
( 2)使用类名加以区分。例如,point::area()和 circle::area()
分别调用类 point和类 circle的 area()函数。
5
5.3 运算符重载
在 C++中,除了可以对函数重载外,还可以对大多数运算
符实施重载。
运算符重载通过创建运算符函数 operator()来实现。
运算符函数定义了重载的运算符将要进行的新的操作,
这种操作通常作用在一个类上。
函数 operator()可以 不是类的成员函数 是类的成
员函数 类的友元函数
6
5.3.1 类以外的运算符重载
对基本的数据类型,C++提供了许多预定义的运算符,如, +‖,,-
‖,,*”,, /‖,,=‖ 等,若有一个复数类 complex,
class complex{
public,
double real,imag;
complex(double r=0,double i=0) {real=r;imag=i;}
};
若要把类 complex的两个对象 com1和 com2加在一起,下面的语句
是不能实现的,
void main()
{
complex com1(1.1,2.2),com2(3.3,4.4),total;
total=com1+com2; //错误
//..,
}
错误原因是类 complex的类型不是基本数据类型,而是用户自定义
的数据类型。 C++还是无法直接将两个 complex类对象相加。
7
运算符函数
为了表达上的方便,人们希望预定义的内部运算符(如, +‖,,-‖、
,*”,, /‖等)在特定类的对象上以新的含义进行解释,如希
望能够实现 total=com1+com2,这就需要用重载运算符来解决。
C++为运算符重载提供了一种方法,即在进行运算符重载时,
必须写一个运算符函数,其名字为 operator后随一个要重载的运
算符。例如,要重载, +‖号,应该写一个名字为 operator+ 的
函数。
表 5.1 运算符函数
函 数 功 能
o p e r a t o r + ( ) 加 法
o p e r a t o r - ( ) 减 法
o p e r a t o r * ( ) 乘 法
o p e r a t o r / ( ) 除 法
o p e r a t o r < ( ) 小 于
……………… ………………
8
运算符函数 operator+()
在编译时遇到名为 operator@的运算符函数( @表示所要重载的运
算符),就检查传递给函数的参数的类型。如果编译器在一个运
算符的两边看到自定义的数据类型,就执行用户自己的函数,而
不是常规运算符。
若要将上述类 complex的两个对象相加,只要写一个运算符函数
operator+()
complex operator+(complex om1,complex om2)
{
complex temp;
temp.real=om1.real+om2.real;
temp.imag=om1.imag+om2.imag;
return temp;
}
我们就能方便的使用语句 total=com1+com2;
将类 complex的两个对象 com1和 com2相加。
也可以使用以下的调用语句,将两个 complex类对象相加,
total=operator+(com1,com2);
这两个调用语句是等价的,但显然后者不如前者简明和方便。
9
例 5.2 运算符函数 operator+()将两个 complex类对象相加程序
#include <iostream.h>
class complex{
public,
double real;
double imag;
complex(double r=0,double i=0)
{real=r;imag=i;}
};
complex operator+(complex co1,complex co2)
{
complex temp;
temp.real=co1.real+co2.real;
temp.imag=co1.imag+co2.imag;
return temp;
}
Void main()
{complex com1(1.1,2.2),com2(3.3,4.4),total1,total2;
total1=operator+(com1,com2); //调用运算符函数 operator+()
的第一种方式
cout<<"real1="<<total1.real<<‖
"<<"imag1="<<total1.imag<<endl;
total2=com1+com2;//调用运算符函数 operator+()的第二种方
cout<<"re l2="<<tot l2.real<<‖
"<<"imag2="<<total2.imag<<endl;
}
程序运行结果为,
real1=4.4 imag1=6.6
real2=4.4 imag2=6.6
10
说明,
( 1)重载运算符与预定义运算符的使用方法完全相同,它不能改
变原有运算符的参数个数(单目或双目),也不能改变原有的优
先级和结合性。
( 2)在 C++中,大多数系统预定义的运算符都能被重载,例如
+ - * / % ^ & |
~ ! = < > += -= *=
/= %= ^= &= |= << >> >>=
<<= == != <= >= && || ++
-- [] () new delete
也有一些运算符是不能被重载的,如,
.,*,,?,
预处理符号 #和 ##也不能重载。
11
5.3.2 成员运算符函数
运算符函数可以定义为类的成员(称为成员运算符函数)
1,成员运算符函数定义的语法形式
成员运算符函数在类中的声明格式为,
class X{
//..,
type operator@(参数表 );
//..,
};
其中 type为函数的返回类型,@为所要重载的运算符符号, X是重
载此运算符的类名,参数表中罗列的是该运算符所需要的操作数。
成员运算符函数定义的更一般形式为,
type X::operator @(参数表 )
{
//函数体
}
在成员运算符函数的参数表中,若运算符是单目的,则参数表为空;
若运算符是双目的,则参数表中有一个操作数。
12
2.双目运算符重载
对双目运算符而言,成员运算符函数的参数表中仅有一个参数,它
们为运算符的右操作数,此时当前对象作为运算符的左操作数,
它们通过 this指针隐含地传递给函数的。 例如,
class X{
//..,
int operator +(X a);
};
在类 X中声明了重载, +‖的成员运算符函数,返回类型为 int,它具
有两个操作数,一个是当前对象,另一个是对象 a。
例 5.3 用双目运算符函数进行复数运算的例子
两个复数 a+bi和 c+di进行加、减、乘、除的方法如下,
加法,(a+bi)+(c+di)=(a+c)+(b+d)i
减法,(a+bi)-(c+di)=(a-c)+(b-d)i
乘法,(a+bi)*(c+di)=(ac-bd)+(ad+bc)i
除法,(a+bi)/(c+di)=((a+bi)*(c-di))/(c*c+d*d)
13
一个复数类 complex,类中含有两个数据成员,即复数的实数部分
#include <iostream.h>
class complex{
private,
double real; //复数的实数部分
double imag; //复数的虚数部分
public,
complex(double r=0.0,double i=0.0); //构造函数
void print(); //显示输出复数
complex operator +(complex c); //重载复数, +‖运算

complex operator -(complex c); //重载复数, -‖运算

complex operator *(complex c); //重载复数, *” 运
算符
complex operator /(complex c); //重载复数, /‖运算

};
complex::complex(double r,double i) //定义构造函数
14
接 1 例 5.3
complex complex::operator +(complex c) //重载, +‖定义
{
complex temp;
temp.real=real+c.real;
temp.imag=imag+c.imag;
return temp;
}
complex complex::operator -(complex c) //重载, -‖定义
{
complex temp;
temp.real=real-c.real;
temp.imag=imag-c.imag;
return temp;
}
15
接 2 例 5.3
complex complex::operator *(complex c) //重载, *” 定

{
complex temp;
temp.real=real*c.real-imag*c.imag;
temp.imag=imag*c.real+real*c.imag;
return temp;
}
complex complex::operator /(complex c) //重载, /‖定义
{
complex temp;
double t;
t=1/(c.real*c.real+c.imag*c.imag);
temp.real=(real*c.real+imag*c.imag)*t;
temp.imag=(c.real*imag-real*c.imag)*t;
return temp;
}
16
接 3 例 5.3
void complex::print() //显示复数的实数部分和虚数部分
{ cout<<real;
if(imag>0)cout<<"+";
if(imag!=0)cout<<imag<<"i\n";
}
void main()
{ complex A1(2.3,4.6),A2(3.6,2.8),A3,A4,A5,A6; //定义六个复
数类对象
A3=A1+A2; //复数相加
A4=A1-A2; //复数相减
A5=A1*A2; //复数相乘
A6=A1/A2; //复数相除
A1.print(); //输出复数 A1
A2.print(); //输出复数 A2
A3.print(); //输出复数相加的结果 A3
A4.print(); //输出复数相减的结果 A4
A5.print(); //输出复数相乘的结果 A5
A6.print(); //输出复数相除的结果 A6
}
程序运行结果如下,
2.3+4.6i
3.6+2.8i
5.9+7.4i
-1.3+1.8i
-4.6+23i
1.017308+0.486538i
17
在主函数 main()中的语句
A3=A1+A2;
A4=A1-A2;
A5=A1*A2;
A6=A1/A2;
所用的运算符, +‖,,-‖,,*”,, /‖是重载后的运算符。程序
执行到这四条语句时 C++将其解释为,
A3=A1.operator +(A2);
A4=A1.operator -(A2);
A5=A1.operator *(A2);
A6=A1.operator /(A2);
由此我们可以看出,成员运算符函数 operator @实际上是由双目
运算符左边的对象 A1调用的,尽管双目运算符函数的参数表只
有一个操作数 A2,但另一个操作数是由对象 A1通过 this指针隐
含地传递的。以上两组语句的执行结果是完全相同的。一般而言,
采用成员函数重载双目运算符 @后,可以用以下两种方法来使用,
aa @ bb; //隐式调用
aa.operator @(bb); //显式调用
成员运算符函数 operator @ 所需的一个操作数由对象 aa通过 this
指针隐含地传递。因此,它的参数表中只有一个操作数 bb。
18
3.单目运算符重载
对单目运算符而言,成员运算符函数的参数表中没有参数,此时当
前对象作为运算符的一个操作数。
例 5.4 重载单目运算符, ++‖的例子
#include <iostream.h>
class coord{
int x,y;
public,
coord(int i=0,int j=0);
void print();
coord operator ++(); //声明成员运算符函数 operator ++()
};
coord::coord(int i,int j) {x=i;y=j;}
void coord::print() {cout<<" x:"<<x<<",y:"<<y<<endl;}
coord coord::operator ++() //定义成员运算符函数 operator ++()
{ ++x;
++y;
return *this;
}
19
接 1 例 5.4
void main()
{ coord ob(10,20);
ob.print();
++ob; //隐式调用成员运算符函数 operator++( )
ob.print();
ob.operator ++(); //显式调用成员运算符函数 operator ++()
ob.print();
}
程序运行结果如下,
x:10,y:20
x:11,y:21
x;12,y=22
本例主函数 main()中调用成员运算符函数 operator++()采用
了两种方法,++ob; 与 ob.operator++()
两者是等价的,其执行效果是完全相同的。
从本例还可以看出,当成员函数重载单目运算符时,没有参数被显
式地传递给成员运算符函数。参数是通过 this指针隐含地传递给
函数的。
20
一般而言
采用成员函数重载单目运算符时,可以用以下两种方法来使用,
@aa; //隐式调用 aa.operator @(); //显式调用
成员运算符函数 operator @所需的一个操作数由对象 aa通过 this
指针隐含地传递。因此,在它的参数表中没有参数。
说明,
( 1)运算符重载函数 operator @()可以返回任何类型,甚至可
以是 void类型但通常返回类型与它所操作的类的类型相同,这样
可以使重载运算符用在复杂的表达式中。例如,在例 5.3中,可
以将几个复数连续进行加、减、乘、除的运算。
( 2)在重载运算符时,运算符函数所作的操作不一定要保持 C++
中该运算符原有的含义。例如,可以把加运算符重载成减操作,
但这样容易造成混乱。所以保持原含义,容易被接受,也符合人
们的习惯。
( 3)在 C++中,用户不能定义新的运算符,只能从 C++已有的运
算符中选择一个恰当的运算符重载。
( 4) C++编译器根据参数的个数和类型来决定调用哪个重载函数。
因此,可以为同一个运算符定义几个运算符重载函数来进行不同
的操作
21
5.3.3友元运算符函数
把运算符函数定义成某个类的友元函数,称为友元运算函数符,
1.友元运算符函数定义的语法形式
友元运算符函数与成员运算符函数不同,后者本身是类中的成员
函数,而前者是类的友元函数。
友元运算符函数在类的内部 声明 格式如下,
friend type operator @ (参数表 );
与成员运算符函数的声明格式相比较,只是在前面多了一个关键字
friend。
定义 友元运算符函数与定义一般的友元函数相似,其格式如下,
type operator @ (参数表 )
{
//函数体
}
由于友元函数不是类的成员函数,所以不需要缀上类名。与成员运
算符函数不同,友元运算符函数是不属于任何类对象的,它没有
this指针。若重载的是双目运算符,则参数表中有两个操作数;
若重载的是单目运算符,则参数表中只有一个操作数。
22
2.双目运算符重载
当用友元函数重载双目运算符时,两个操作数都要传给运算符函数。
例 5.5 友元函数重载双目运算符
在例 5.3中,用成员运算符函数进行复数运算,现在我们采用友元
运算符函数。
#include<iostream.h>
class complex{
private,
double real;
double imag;
public,
complex(double r=0.0,double I=0.0) {real=r; imag=i;}
void print();
friend complex operator + (complex a,complex b);
//用友元运算符函数重载复数, +‖
friend complex operator – (complex a,complex b);
//用友元运算符函数重载复数
,-‖
friend complex operator * (complex a,complex b);
//用友元运算符函数重载复数, *”
friend complex operator / (complex a,complex b);
//用友元运算符函数重载复数, /‖
23
接 1 例 5.5
complex operator +(complex a,complex b) //重载,+‖定义
{ complex temp;
temp.real=a.real+b.real; temp.imag=a.imag+b.imag;
return temp;
}
complex operator-(complex a,complex b) //重载,-‖定义
{ complex temp;
temp.real=a.real-b.real; temp.imag=a.imag-b.imag;
return temp;
}
complex operator (complex a,complex b) //重载,*”定义
{ complex temp;
temp.real=a.real*b.real-a.imag*b.imag;
temp.imag=a.real*b.imag+a.imag*b.real;
return temp;
}
24
接 2 例 5.5
complex operator/(complex a,complex b) //重载,/‖定义
{ complex temp;
double t;
t=1/(b.real*b.real+b.imag*b.imag);
temp.real=(a.real*b.real+a.imag*b.imag)*t;
temp.imag=(b.real*a.imag-a.real*b.imag)*t;
return temp;
}
void complex::print() //输出显示复数
{
cout<<real;
if(imag>0) cout<<―+‖;
if(imag!=0) cout<<imag<<―i\n‖
}
25
void main()
{ complexA1(2,3,4.6),A2(3,6,2.8),
A3,A4,A5,A6;//定义 6个复数对象
A3=A1+A2; //复数相加
A4=A1-A2; //复数相减
A5=A1*a2; //复数相乘
A6=A1/A2; //复数相除
A1.print(); //输出复数 A1
A2.print(); //输出复数 A2
A3.print();//输出复数相加结果 A3
A4.print();//输出复数相减结果 A4
A5.print();//输出复数相乘结果 A5
A6.print();//输出复数相除结果 A6
}
程序运行结果如下,
2.3+4.6i 5.9+7.4i
3.6+2.8i -1.3+1.8i
-4.6+23i 1.017308+0.486538i
26
3.单目运算符重载
用友元函数重载单目运算符时,需要一个显式的操作数。
例 5.6 用友元函数重载单目运算符, -”例子。
#include<iostream.h>
class nclass{
int a,b;
public,
nclass(int x=0,int y=0) {a=x;b=y;} // nclass的构造函数
friend nclass operator-(nclass obj);//声明重载单目运算符,-‖
void show();
};
nclass operator-(nclass obj)//定义重载单目运算符,-‖
{ obj.a=-obj.a; obj.b=-obj.b; return obj; }
void nclass::show()
{ cout<<―a‖‖<<a―<‖=‖<<b<<end }
程序运行结果如下,
a=10 b=20
a=-10 b=-20
mian()
{ nclass
ob1(10,20),ob2;
ob1.show();
ob2=-ob1;
ob2.show();
return 0;
}
27
使用友元函数重载, + +”,,- -”这样的运算符,可能会出现的问
题 在例 5.4用成员函数重载, ++”的成员运算符函数,
coord coord::operator++()
{ ++x;
++y;
return *this;
};
由于所的成员函数都有一个 this指针,this指针指向该函数所属类
对象的指针,因此对私有数据的任何修改都将影响实际调用成员
运算符函数的对象,因而例 5.4的执行情况是完全正确的。
如果用友元函数改写上面的运算符函数,
coord operator++(coord op)
{ ++x; // ++op.x;
++y; // ++op.y;
return op;
};
28
由于友元函数没有 this指针,所以不能引用 this指针所指的对象。
这个函数是通过传值的方法传递参数的,函数体内对 op的所以修改
都无法传到函数体外。因此,在 operator++函数中,任何内容的改
变不会影响产生调用的操作数,也就是说。实际上对象 x和 y并未增
加。
为了解决以上问题,使用友元函数重载单目运算符, ++”或, —”时
,应采用引用参数传递操作数,这样函数参数的任何改变都影响产
生调用的操作数,从而保持了两个运算符的原意。
29
例 5.7 使用友元运算符函数重写例 5.4的程序。
#include<iostream.h>
class coord{
int x,y;
public,
coord(int i=0,int j=0) {x=i;y=j;}
void print() {cout<<― x:” <<x<<―, y:,<<y<<endl;}
friend coord operator ++(corrd&op);
//声明友元运算符函数,形参为引用
};
coord operator ++(coord&op) //定义友元运算符函数
{ ++op.x;
++op.y;
return op;
}
Void main()
{ coord ob(10,20);
ob.print();
++ob; //隐式调用友元运算符函数
ob.print();
operator++(ob);//显式调用友元运算符函数
ob.print();
}
程序运行的结果如下,
x,10,y,20
x,11,y,21
x,12,y,22
30
当用友元函数重载单目运算符时,参数表中有一个操作数
一般而言,采用友元函数重载单目运算符 @后,可以采用以下
两种方法来使用,
@aa; //隐式调用
operator@(aa); //显式调用
说明,
( 1)不能用友元函数重载的运算符是,=,(),[ ],-> 其余的
运算符都可以使用友元函数来实现重载。
( 2)由于单目运算符,-‖可不改变操作数自身的值,所以在例 5.6
重载单目运算符,-‖ 的友元运算符函数的原型可写成,
friend nclass operator –(nclass obj);
通过传值的方式传递参数。
31
5.3.4成员运算符函数与友元运算符函数的比较
我们对成员运算符函数与友元运算符函数作一比较。
( 1)对双目运算符,成员运算符函数带有一个参数,友元运算符函数
带有两个参数对单目运算符,成员运算符函数不带参数,友元运算
符函数带有一个参数。
(2)双目运算符一般可以被重载为友元运算符函数或成员运算符函
数,但有一种情况,必须使用友元函数。
例如,在例 5.6的 类 nclass 中,用成员运算符函数重载, +”运算
nclass nclass::operator+(int x)
{
nclass temp;
temp.a=a+x;
temp.b=b+x;
return temp;
}
32
若类 nclass的对象 ob要做赋值运算和加法运算,以下是一条正确的
语句,
ob=ob+100;
由于对象 ob是运算符, +”的左操作数,所以它调用了, +”运算符重
载函数,把一个整数加到了对象 ob的某些元素上去。然而,下一条
语句就不能工作了,
ob=100+ob;
不能工作的原因是运算符的左操作数是一个整数,而整数是一个内
部数据类型,100不能产生对成员运算符函数的调用。
33
用两种形式的友元函数来重载运算符函数, +”
就能消除由于运算符, +”的左操作数是内部数据类型而带来的问题,
下述程序说明了如何实现。
例 5.8用两种形式的友元函数来重载运算符函数, +”
#include<iostream.h>
class nclass{
int a,b;
public,
nclass(int x=0,int y=0) {a=x;b=y}
friend nclass opterator +(nclass ob,int x);
//声明一种友元运算符函数
friend nclass opterator +(int x,nclass ob);
//声明另一种友元运算符函数
void show();
};
34
接 1 例 5.8
nclass operator +(nclass ob,int x)
//定义常规类型在右边的友元运算符函数
{ nclass temp;
temp.a=a+x;
temp.b=b+x;
return temp;
}
nclass operator +(int x,nclass ob)
//定义常规类型在左边的友元运算符函数
{
nclass temp;
temp.a=x+ob.a;
temp.b=x+ob.b;
return temp;
}
void nclass::show()
{cout<<―a=‖<<a<<‖ ―<<―b=―<<b<<―\n‖; }
void main()
{ nclass ob1(30,40),ob2;
ob2=ob1+30;
ob2.show();
ob2=50+ob1;
ob2.show();
}
35
( 3) ( 4)
( 3)成员运算符函数和友元运算符函数都可以用习惯方式调用,
也可以用它们专用的方式调用,表 5.2列出了一般情况下运算符
函数的调用形式。
习惯形式 友元运算符函数调用形式 成员运算符函数调用形式
a+b operator+(a,b) a.operator+(b)
-a operator-(a) a.operator-()
a++ operator++(a,0) a.operator++(0)
( 4) c++的大部分运算符既可说明为成员运算符函数,又可说明
为友元运算符函数。究竟选择哪一种运算符函数好一些,没有定
论,这主要取决于实际情况和程序员的习惯。
一般而言,对于双目运算符,将它重载为一个友元运算符函数
比重载为一个成员运算符函数便于使用。若一个运算符的操作需
要修改类对象的状态,则选择成员运算符函数较好。如果运算符
所需的操作数(尤其是第一个操作数)希望有隐式类型转换,则
运算符重载必须用友元函数,而不能用成员函数。
36
5.3.5“++”和, - -”的重载
运算符, ++”和, - -”放置在变量的前面与后面,其作用是有区别
的。但是 C++2.1之前的版本在重载, ++”(或, - -”)时,不能
显式地区分是前缀还是后缀方式。也就是说,在例 5.7的 main()
函数中,以下两条语句是完全相同的,
ob++; ++ob;
在 C++2.1及以后的版本中,编辑器可以通过在运算符函数参数表中
是否插入关键字 int 来区分这两种方式。
(1) 前缀方式 ++ob,可以用运算符函数重载为,
ob.operator++(); //成员函数重载
operator++(X&ob); //友元函数重载,其中 ob为 X类对象
(2)后缀方式 ob++,可以用运算符函数重载为,
ob.operator++(int); //成员函数重载
operator++(X&ob,int); //友元函数重载
调用时,参数 int 一般用 0值。
重载运算符, - -”也可用类似的方法。
37
例 5.9包含, ++”和, --”两种重载运算符
#include<iostream.h>
#include<iomanip.h>
class over{
int i1,i2,i3;
public,
void init(int I1,int I2,int I3) {i1=I1; i2=I2; i3=I3;}
//给数据成员赋初值
void print(); {cout<<― i1:” <<i1<<‖ i2:” <<i2<<‖
i3:” <<i3<<endl; //显示输出数据成员
over operator++(); //成员函数重载,++‖(前缀方式)声

over operator++(int); //成员函数重载,++‖(后缀方式)声

friend over operator- -(over&) ;
//友元函数重载,- -‖(前缀方式)声

friend over operator- -(over&,int);
//友元函数重载,- -‖(后缀方式)声
38
接 1 例 5.9
over over::operator++() //定义成员函数重载,++‖(前缀方式)
{ ++i1; ++i2; ++i3;
return *this,
}
over over::operator++(int) //定义成员函数重载 ++‖(后缀方式)
{ i1++; i2++; i3++;
return *this;
}
over operator- -(over&op) //定义友元函数重载,- -‖(前缀方
式)
{ - -op.i1; - -op.i2; - -op.i3;
return op;
}
over operator- -(over&op,int)//定义友元函数重载,- -‖(后缀方
式)
{ op.i1- -; op.i2- -; op.i3- -;
return op;
}
39
接 2 例 5.9
Void main()
{ over obj1,obj2,obj3,obj4;
obj1.init(4,2,5);
obj2.init(2,5,9);
obj3.init(8,3,8);
obj4.init(3,6,7);
++obj1; //隐式调用 over operator++()
obj2++; //隐式调用 over operator++(int)
--obj3; //隐式调用 over operator- -(over&)
obj4--; //隐式调用 over operator- -(over&,int)
obj1.print(); obj2.print(); obj3.print(); obj4.print();
cout<<‖---------------\n‖;
obj1.operator++(); //显式调用,意为 ++obj1
obj2.operator++(0); //显示调用,意为 obj2++
operator--(obj3); //显示调用,意为 - -obj3
operator--(obj4,0); //显示调用,意为 obj4- -
obj1.print(); obj2.print(); obj3.print(); obj4.print();
}
程序运行结果如下,
i1:5 i2:3 i3:6
i1:3 i2:6 i3:10
i1:7 i2:2 i3:7
i1:2 i,5 i3:6
---------------
i1:6 i2:4 i3:7
i1:4 i2:7 i3:11
i1:6 i2:1 i3:6
i1:1 i2:4 i3:5
以上例子中,使用成员函数分别以前缀方式和后缀方式重载了运算
符,++‖,使用友元函数分别以前缀方式和后缀方式重载了运算符
,- -‖。
40
5,3,6赋值运算符, =”的重载
对任一类 X,如果没有用户自定义的赋值运算符函数,那么系统将
自动地为其生成一个缺省的赋值运算符函数,例如,
X﹠ X:,operator=(const X ﹠ source)
{
//成员间赋值
}
若 obj1和 obj2是类 X的两个对象,obj2已被创建,则编译程序遇到
如下语句,
obj1=obj2;
就调用缺省的赋值运算符函数,将对象 obj2的数据成员逐域拷贝到
对象 obj1中。
通常, 缺省的赋值运算符函数是能够胜任工作的 。 但在某些特殊情
况下, 如类中有指针类型时, 使用缺省赋值运算符函数会产生错
误 。 请看下面的例子 。
41
例 5,10
#include<iostream.h>
#include<string.h>
class string{
char *ptr;
public,
string(char *s)
{ ptr=new char[strlen(s)+1];
strcpy(ptr,s);
}
~string() { delete ptr; }
void print() { cont<<ptr<<endl; }
};
程序运行结果如下,
p2:chen
p1,♀ ♀
Null Point assignment
void main()
{ tring p1("chen");
{ string p2(" ");
p2=p1; //string 类对象间赋值
cont<<"p2:";
p2.print();
}
cout<<"p1:";
p1.print();
}
42
接 1 例 5.10
运行上述程序输出 p1时发生错误。原因是执行到赋值语句 p2=p1时,
实际是使对象 p2的指针 ptr与对象 p1的指针 ptr指向 new开辟的同
一个空间。当 p2的生命周期(内层的一对花括号间)结束时,系
统自动调用析构函数将这一空间撤消。这样,p2中的 ptr原来所
指向的区域被封锁起来,并不能再被使用,这就产生了指针悬挂
问题。这时尽管 p1的指针 ptr存在,其所指向的空间却无法访问
了,如图所示。当 p1的生命走其结束时,将再次调用析构函数,
释放同一空间,从而导致运行错误。
43
动态空间 1 动态空间 2
ptr ptr
P1,P2,
(a) 执行 P2=P1之前
动态空间 1 动态空间 2
ptr ptr
P1,P2,
(b) 执行 P2=P1之后
(c)p2的生命周期结束后
44
显式地定义一个赋值运算符重载函数
例 5,11显式地定义一个赋值运算符重载函数,使 P1和 P2有各自的
存储区。 #include<iostream.h>
#include<string.h>
class string{
char *ptr;
public,
string(char *)
{ ptr=new char[strlen(s)+1];
strcpy(ptr,s);
}
~string() { delete ptr; }
void print() {count<<ptr<<endl;}
string &operator=(const string&); //声明赋值运算符重载函

};
string &string::opertor=(const string &s) //定义赋值运算符重载
{ if(this==&s)return *this;
delete ptr;
ptr=new char[strlen(s.ptr)+1];
strcpy(ptr,s.ptr);
return *this;
}
void main()
{ string p1("chen");
{ string p2 (" ");
p2=p1;
cout<<"p2:";
p2.print();
}
cout<<"p1:";
p1.print();
}
程序运行结果如下,
p2:chen
p1:chen
45
说明,
(1)赋值运算符, =”只能重载为成员函数,而不能把它重载为友元
函数,因为若把上述赋值运算符, =”重载为友元函数,
friend string &operator=(string &p2,string &p1)
这时,表达式,p1="chen‖
将被解释为,operator=(p1,―chen‖) 这显然是没有什么问题的,
但对于表达式 "chen"=p1
将被解释为,operator=("chen",p1)
即 C++编译器首先将,chen‖转换成一个隐藏的 string对象,然后
使用对象 p2引用该隐藏对象,编译器不认为这个表达式是错误
的,从而将导致赋值语句上的混乱。因此双目赋值运算符应重载
为成员函数的形式,而不能重载为友元函数的形式。
( 2)类的赋值运算符,=‖可以被重载,但重载了的运算符函数
operator=()不能被继承。
46
5,3,7 函数调用运算符, (), 与下标运算符, []”的重载
1.重载函数调用运算符“()”
C++中函数的形参和实参都用,( )‖括号括起,函数调用运算符一
般使用格式为,
基本表达式( 〈 表达式表 〉 )
可以认为函数调用运算符“()”是一个双目运算符,其操作数为
〈 基本表达式 〉 即函数名和 〈 表达式表 〉 (可能为空)。
我们可以对运算符“()” 进行重载,使它起别的作用 。
设 obj是类 myclass的对象,而且类 myclass中定义了重载函数调
用运算符“()”,它起的作用是能把对象 obj的数据扩大 k 倍和
m 倍。
对应的运算符函数为 myclass operator()(int k,int m)
则函数调用 (把对象 obj的数据扩大 10 倍和 100 倍 )
obj(10,100);
可被解释为,obj.operator()(10,100);
一般的重载运算符“()”,类 X operator()(arg1,arg2,……)
obj(arg1,arg2,…………) ;
可被解释为,obj.operator()(arg1,arg2,……) ;
47
重载运算符, (), 例
我们知道,双目运算符是由左边的对象产生对 operator函数调用的。
This指针总是指向产生调用的对象。重载运算符,()‖的
operator函数可以带任何类型的操作数,返回任何有效的类型。
下面的程序将帮助我们理解 函数调用 运算符,()‖的重载。
例 5,12运算符“()的重载。
#include<iostream.h>
class myclass{
int i;
float j;
public,
myclass(int x=0,float y=0) { I=x; j=y; }
myclass operator( )( int k,float m );
//声明重载运算符“()”函

void display() { cout<<I<<‖ "<<j<<"\n"; }
};
myclass myclass::operator( )(int k,float m) //定义运算符“()”
函数
{ i=i*k; j=j*m;
return *this;
}
程序运行结果为,
10 11.5
100 1150.0
说明:函数调用运算符“()”必须重载为一个成员函

void main()
{ myclass obj1(10,11.5);
obj1.display();
obj1(10,100); //重载运算符“()”的调用,可解
// 释为 obj1.perator()(10,.100)
obj1.display();
}
48
2.重载下标.运算符,[ ]‖
C++中运算符,[ ]‖可作数组下标,下标运算符的一般使用格式为,
〈 基本表达式 〉 [〈 表达式 〉 ]
C++把它看成双目运算符,其操作数为 〈 基本表达式 〉 即数组名和
〈 表达式 〉 即下标。
我们可以对运算符,[ ]‖ 进行重载,使它起别的作用 。
设 obj是类 myclass的对象,而且类 myclass中定义了重载下标运
算符,[ ]‖,它起的作用是能把对象 obj中的某个数据取下标。
在重载,[ ]‖时,C++也把它看成双目运算符,其操作数为 〈 基
本表达式 〉 和 〈 表达式 〉 。
相应的运算符函数为 myclass operator[ ](int sub)
则表达式 obj[5]; //取 obj中 某个数据的第 6个
可被解释为,obj.operator[ ](5);
对运算符, [ ]‖ 重载定义只能使用成员函数,其形式如下,
类型 类名:,operator[ ] (形参 )
{
函数体
} 注意:形参只能有一个。
49
对 string数据类型定义以下的,[ ]‖运算符重载函数,
例如,可以对 string数据类型定义以下的,[ ]‖运算符重载函数,
class string{
char *ptr;
//………………
public,
string(const char *);
char &operator[ ](int);
//………………
};
其中重载,[ ]‖的 operator函数定义为,
char & string::opertor[ ](int I) { return ptr[I]; }
由于 string::operator[ ]( )的返回值在这里是一个 char&,所以函数
可以被用于一个赋值运算符的任一边,例如,
string op= "abcd";
op[1]=op[3];
相当与,op.operator[ ](1)=op.operator[ ](3);
因而 op的新值为, adcd"。
50
重载运算符,[ ]‖来处理数组成员
例 5,13重载运算符,[ ]‖来处理例子
#include<iostream.h>
#include<iomanip.h>
#include<string.h>
#include<stdlib.h>
chass sales{
char compName[25]; //定义字符串
int divisionTotals[5]; //定义整数数组成员
public,
void init(char CN[ ]) { strcpy(compName,CN); }
int &operator[ ](int); //重载运算符,[ ]‖函数的声

char *getName(void) { return compName; }
};
int &sales::operator[ ](int sub) //定义重载运算符,[ ]‖函数
{ if (sub<0||sub>4) //检查数据边界
{ cerr<<"Bad subscript!"<<sub<<"is not allowed.\n";
exit(1);
}
return divisionTotals[sub];
}
51
接 1 例 5.13
void main()
{ int totalSales=o,avgSales;
sales company; //定义对象
company.init("Swiss Cheese");
company[0]=123; //调用重载运算符,[ ]‖,初始化
divisonTotals[ ]
company[1]=456; company[2]=676;
company[3]=234; company[4]=786;
cout<<"Here are the sales for"<<company.getName()
<<" 's divisions:\n";
for (int i=0;i<5;i++)
{ cout<<company[i]<<―\n‖;} //调用重载运算符,[ ]‖
for(i=0;i<5;i++)
{ totalSales+=company[i];} //调用重载运算符,[ ]‖
cout<<"The total sales are:"<<totalSales<<"\n";
avgSales=totalSales/5;
cout<<"The vaerage sales are:"<<avgSales<<"\n";
}
52
接 2 例 5.13
程序运行如下,
Here are the sales for Swiss Cheese's divisions,
123
456
676
234
786
The total sales are 2275
The average sales are 455
在上述程序中,并没有创建 company的对象数组。程序中的下标
被重载,以便在使用 company时用一种特殊的方式工作。下标
总是返回和下标对应的那个部门销售额 dividionTotals[]。
重载下标运算符,[]‖时,返回一个 int的引用,可使重载的,[]‖
用的赋值语句的左边,因为在 main () 中,可对某个部门的销售
额 dicisionTotal[ ]赋值。这样,虽然 divisionTotals[]是私有的,
main()还是能够直接对其赋值,而不需要使用函数 init()
53
在上述程序中,设有对下标的检验,以确保被赋值的数组元素的存
在,当程序中一旦出现向超出所定义的数组下标范围的数组元素的
赋值,便自动终止程序,以免造成不必要的破坏。
与函数调用运算符,()‖一样,下标运算符,[ ]‖不能用友元函数重
载,只能采用成员函数重载。
54
5.3.8类型转换
1.系统预定义类型间的转换
类型转换是将一种类型的值转换为另一种类型值 。 对于系统预定
义类型, C++提供两种类型转换方式, 一种是隐式类型转换 (或称标
准类型转换 ),另一种是显式类型转换 。
( 1) 隐式类型转换
隐式类型转换主要遵循以下规则,
① 在赋值表达式 A=B的情况下, 赋值运算符右端 B的值需转换为 A
类型后进行赋值。
②当 char或 short类型变量与 int 类型变量进行运算时, 将 char或
short 类型转换成 int 类型。
③当两个操作对象类型不,一致时, 在算术运算前, 级别低的自动
转换为级别高的类型 。
55
( 2) 显式类型转换
显式类型常用下述方法表示,
① 强制转换法 强制转换法的格式为:(类型名)表达式
例如,int i,j
// …………
cout<<(float)(i+j); 将整数 i+j的值强制转换成 float型后输出。
② 函数法
函数法函数法的转换格式为,
类型名(表达式)
在上面的例子中,若采用函数法则变为,
int i,j;
//…………
cout<<float(i+j)
此时也将 I+j的值强制转换成 float型后输出。
56
2,类类型与系统预定义类型间的转换
前面介绍的是一般数据类型之间的转换。那么,对于用户自己定义
的类类型来讲,如何实现它们与其它数据类型之间的转换呢?通常,
可归纳为以下三种方法,
即通过 构造函数,通过 类型转换函数,通过 运算符重载 进行转换。
(1)通过构造函数进行类型转换
构造函数具有类型转换的作用,它将 其他类型的值装换为它所在类
的类型的值 。
57
例 5,14
#include<iosteam.h>
class example{
private,
int num;
public,
example(int);
void print ();
};
exmple::example(int n)
{ num=n;
cout<<"Inializing with,"<<num<<endl;
}
void example::pringt()
{ cout<<"num="<<num<<endl; }
在类 example中有一个参数为 int 的构造函数 example(int),此构造
函数可以用来进行类型转换,即完成由 int型向 example类类型
的转换。
58
void main()
{ example X=example(3); ①
X.print();
cout<<"————————\n";
example Y=6; ②
Y.print();
}
程序执行结果如下,
Intializing with:3
Num=3
————————
Intializing with:6
num=6
Main ()函数中的语句①将整数3转换为类类型 example后赋给对象
X,若没有 example(int)构造函数,此语句将是被禁止的。语句②将
6转换为类类型 example后赋给对象Y,转换也是通过构造函数
example( int)完成的。
59
(2)通过类型转换函数进行类型转换
通过构造函数可以进行类型转换,但是它的转换功能受到限制。它
只能从基本类型(如 int,float等)向类类型转换,而想将一个类
类型向基本类型转换是办不到的。 类型转换函数则可以用来把用
户定义的类类型转换成基本类型 。它是一种类似显式类型转换的
机制。在类中,类型转换函数定义的一般格式为,
class X{
//…………
operator type( )
{ //…………
return type 类型的数据;

};
其中 type为要转向的目标类型(通常是基本类型)。这个函数既没
有参数,又没有返回类型,但在函数体中必须返回具有 type类型
的一个数据。类类型转换函数的功能是将类X的对象转换为类型
为 type的数据。 使用类型转换函数的方法与对基本类型强制转换
一样,例如,X obj; int(obj)
60
例5.15
#include<ostream.h>
class complex{
private,
float real,imag;
public,
complex(float r=0,float i=0)
{ real=r; imag=i;
cout<<"Constructing… \n";
}
operator float()
{ cout<<"Type changed to float… \n";
return real;
}
operator int()
{ cout<<'Type changed to int … \n";
return int (real);
}
61
void print()
{ cout<<'('<<real<<','<<imag<<')'<<endl;
}
};
void main()
{ complex A(2.2,4.4);
A.print();
cout<<float(A)*0.5<<endl;
complex B(4,6);
B.print();
cout<<int(B)*2<<endl;
}
程序运行结果如下,
constructing ……
(2.2,4.4)
Type changed to float……
1.1
Type changed to int……
8
在以上程序中,两次调用了类型转
换函数。第一次采用显式调用的方
式,将类 complex的对象A转换成
float类型。第二次采用显式调用
的方式,将类 complex的对象B转
换成 int 类型。
62
说明,
① 类型转换函数只能定义为一个类的成员函数而不能定义为类的友
元函数。类型转换函数与普通的成员函数一样,也可以在类定义
体中声明函数声明,而将函数体定义在类外部。
②在一个类中可以定义多个类型转换函数。C++编译器将根据操
作数的类型自动的选择一个合适的类型转换函数之匹配。当在可
能出现二义性的情况下,应显式的使用类型转换函数进行类型转
换。
③运算符重载与类型转换在某些情况下,用户希望将基本类型的数
据与类对象进行运算。通过运算符重载可以提供这种方法。例如
下列程序中通过重载运算符, +,,是类 complex的对象能够与
一个整形变量相加。
63
例 5.16
#include<iostream.h>
class complex{
private,
float real,imag;
public,
complex(float r=0,float I=0)
{ real=r; imag=I; }
friend complex opeator+(int x,complex y)
{ complex temp;
temp.real=x+y.real;
temp.imag=y.imag;
return temp;
}
void print() {cout<<'('<<eal<<','<<imag<<')'<<endl;}
};
void main( )
{ complex ob1(10.5,20.5),ob2;
int x=100;
ob1.print( );
ob2=x+ob1; //整形变量 x与
//类 complex的对象 ob1相加
ob2.print( );
}
程序运行结果如下,
(10.5,20.5)
(110.5,20.5)
64
5.4虚函数
虚函数 是一种动态的 重载方式,虚函数允许函数调用与函数体之间
的联系在运行时才建立,也就是在运行时才决定如何动作,即所
谓 动态联编。
5.4.1引入派生类后的对象指针
引入派生类后,由于派生类是由基类派生出来的,因此指向基类的
指针和指向派生类的指针是相关的。
例 5.17#include <iostream.h>
class my_base{ //定义基类
int a,b;
public,
my_base(int x,int y)
{ a=x; b=y; } //基类的构造函数
void show( )
{ cout <<―基类 my_base----\n‖;
cout <<a<<― ―<<b<<endl;
}
};
65
接 1 例 5.17
class my_class:public my_base{ //定义 派生类
int c;
public,
my_class(int x,int y,int z):my_base(x,y)
{ c=z; } //派生类 的构造函数
void show( )
{ cout <<―派生类 my_class----\n‖;
cout <<―c=―<<c<<endl;
}
};
Void main( )
{ my_base mb(50,50),*mp;
my_class mc(10,20,30);
mp=&mb;
mp->show();
mp=&mc;
mp->show();
}
程序运行结果如下,
基类 my_base----
50 50
基类 my_base----
10 20
66
mp 指向 派生类对象 mc,但它调用的函数 mp->show()仍然是基类
的 show()。出问题了。要解决这个问题要用 虚函数。
( 1) 基类的指针可以指向它的公有派生类的 对象。但不允许 指向
它的私有派生类的 对象 。
( 2) 派生类的指针 不允许 指向它的基类的 对象。
( 3) 基类的指针指向它的公有派生类的 对象时,只能用它来直接
访问 派生类 中从 基类 继承来的成员,而不能直接访问 公有派生类 中
定义的新成员。
67
5.4.2 虚函数的定义及使用
1,虚函数的作用
基类的指针指向它的公有派生类的 对象时,访问 公有派生类 中定义
的新成员,可 采用 显式的方法,mc.show( );
或采用 对指针强制类型转换的方法,((my_class *)mp)->show( );
这二种方法都没有达到动态的效果, 其实只要将函数 show( )说明为
虚函数, 就能实现这种动态调用的功能 。
例 5.18 虚函数的作用
#include <iostream.h>
class my_base{ //定义基类
int a,b;
public,
my_base(int x,int y)
{ a=x; b=y; } //基类的构造函数
virtual void show( ) //定义 虚函数
{ cout <<―基类 my_base----\n‖;
cout <<a<<― ―<<b<<endl;
}
};
68
接 1 例 5.18
class my_class:public my_base{ //定义 派生类
int c;
public,
my_class(int x,int y,int z):my_base(x,y)
{ c=z; } //派生类 的构造函数
void show( ) //重新 定义 虚函数
{ cout <<―派生类 my_class ----\n‖;
cout <<―c=―<<c<<endl;
}
};
void main( )
{ my_base mb(50,50),*mp;
my_class mc(10,20,30);
mp=&mb;
mp->show();//调用基类 my_base的 show()版本
mp=&mc;
mp->show();//调用 派生类 my_class的 show()版本
}
程序运行结果如下,
基类 my_base----
50 50
派生类 my_class---
30
69
2,虚函数的定义
关键字 virtual指示 C++编译器,函数调用 my->show( )要在运行时确
定所要调用的函数,即要对该调用进行动态联编。因此,程序在
运行时根据指针 mp所指向的实际对象,调用该对象的成员函数。
虚函数就是在基类中被关键字 virtual说明,并在派生类中重新定义
的函数 。在派生类中重新定义时,其函数原型,包括返回类型、
函数名、参数个数与参数类型的顺序,都必须与基类中的原形完
全相同 。
例 5.19 多派生的虚函数
#include <iostream.h>
class parent {
protected,
char version;
public,
parent( ) { version=?A‘; }
virtual void print( ) //定义 虚函数 print()
{ cout <<―\n The parent.version‖<<version\n‖; }
}; //定义 基类 parent
70
接 1 例 5.19
class derived1, public parent { //定义 派生类 derived1
private,
int info;
public,
derived1(int number)
{ info=number;
version=?1‘;
}
void print( ) //重新 定义 虚函数 print( )
{cout<<―\n The derived1
info:‖<<info<<―version‖<<version<<endl; }
};
71
接 2 例 5.19
class derived2, public parent { //定义 派生类 derived2
private,
int info;
public,
derived2(int number)
{ info=number; }
void print( ) //重新 定义 虚函数 print( )
{cout<<―\n The derived2
info:‖<<info<<―version‖<<version<<endl; }
};
72
接 3 例 5.19
void main( )
{ parent ob,*op;
op=&ob;
op->print(); //调用基类 parent的 print()
derived1 d1(3);
op=&d1;
op->print(); //调用派生类 derived1的 print()
derived2 d2(15);
op=&d2;
op->print(); //调用派生类 derived2的 print()
}
程序运行结果如下,
The parent.version A
The derived1 info:3 version 1
The derived2 info:15 version A
73
对虚函数的定义作几点说明
(1),在基类中,用关键字 virtula可以将成员函数声明为虚函数。
(2),在派生类对基数中声明的虚函数进行重新定义时,关键字
virtual可以写也可以不写。但在容易引起混乱的情况下,最好在
对派生类的虚函数进行重新定义时也加上关键字 virtual。
(3),虚函数被重新定义时,其函数的原型与基类中的函数原型必须
完全相同。
(4),指向基类的指针 op允许指向其派生类。不断改变它所指向的对
象,而且这些动作都是在运行时动态实现的。可见用虚函数充分
体现了面向对象程序设计的动态多态性。
(5),对象名和点运算符的方式也可以调用虚函数。但调用是在编译
时进行的静态联编。
(6),一个虚函数无论被公有继承多少次,它仍然保持其虚函数的特
性。
(7),虚函数的成员函数,是静态成员函数。
(8),构造函数不能是虚函数,但析构函数可以是虚函数。
74
3,虚函数与重载函数的关系
在一个派生类中重新定义基类的虚函数是函数重载的另一种形式,
但它不同于一般的函数重载。当普通的函数重载时,其函数的参
数或参数类型必须有所不同,函数的返回类型也可以不同。但是,
重新定义虚函数时,要求函数名、返回类型、参数个数、参数的
类型和顺序与基类中的虚函数原型完全相同。
例 5.20 虚函数和普通的函数重载的比较
#include <iostream.h>
class base {
public,
virtual void func1( );
virtual void func2( );
virtual void func3( );
void func4( );
};
75
接 1 例 5.20
class derived, public base {
public,
virtual void func1( ); //是 虚函数重新 定义
void func2(int x);//因有 int x,作为普通 函数重载
char func3( ); //错误,返回 类型不同,应删除
void func4( ); //是 普通的函数重载,不 是 虚函数
}
void base::func1() { cout<<―----base func1----\n‖; }
void base::func2() { cout<<―----base func2 ----\n‖; }
void base::func3() { cout<<―----base func3----\n‖; }
void base::func4() { cout<<―----base func4----\n‖; }
void derived::func1() { cout <<―----derived func1----\ n‖; }
void derived::func2(int x) { cout<<―----derived func2----\n‖; }
void derived::func4() { cout<<―----derived func4----\n‖; }
76
接 2 例 5.20
void main()
{ base d1,*bp;
derived d2;
bp=&d2;
bp->func1(); //调用 derived::func1()
bp->func2(); //调用 base::func2()
bp->func4(); //调用 base::func4()
}
程序运行结果如下,
----derived func1----
----base func2 ----
----base func4 ----
77
4,多重继承与虚函数
多重继承可以视为多个单继承的组合。因此,多重继承情况下的虚
函数调用与单继承情况下的虚函数调用有相似之处。
例 5.21 多重继承下的虚函数
#include <iostream.h>
class base1 {
public,
virtual void fun() //定义 fun()是 虚函数
{ cout<<―--------base1--------\n‖; }
};
class base2 {
public,
void fun() //定义 fun()为 普通的成员函数
{ cout<<―--------base2--------\n‖; }
};
class derived:public base1,public base2 {
public,
void fun()
{ cout<<―--------derived--------\n‖; }
};
78
接 1 例 5.21
void main()
{ base1 *ptr1; //定义指向基类 base1的指针 ptr1
base2 *ptr2; //定义指向基类 base2的指针 ptr2
derived obj3; //定义派生类 derived的对象 obj3
ptr1=&obj3;
ptr1->fun();
// 此处的 fun()为虚函数,因此调用派生类 derived的 fun()
ptr2=&obj3;
ptr2->fun(); // 此处的 fun()为非虚函数,而 ptr2又为 base2的指针,
// 因此调用基类 base2的 fun()
}
程序运行结果如下,
--------derived----------
--------base2--------
79
5,虚函数举例
例 5.22 应用 C++的 多态性,计算三角形、矩形和圆的面积
#include <iostream.h>
class figure { //定义一个公共基类
protected,
double x.y;
public,
figure(double a,double b) { x=a; y=b; }
virtual void show_area() //定义一个虚函数,作为界面接口
{ cout<<―No area computation defined for this class.\n‖; }
};
class triangle:public figure{ //定义派生类三角形
public,
triangle(double a,double b):figure(a,b) { };
void show_area() //虚函数重新 定义,求 三角形的面积
{ cout <<―三角形的高 =‖<< x <<― 三角形的底 =‖<<y<<endl;
cout<<―三角形的面积 =‖<< x*y*0.5<<endl;
}
};
80
接 1 例 5.22
class square:public figure{ //定义派生类矩形
public,
square(double a,double b):figure(a,b) { };
void show_area() //虚函数重新 定义,求 矩形的面积
{ cout <<―矩形的长 =‖<< x <<― 矩形的宽 =‖<<y<<endl;
cout<<―矩形的面积 =‖<< x*y<<endl;
}
};
class circle:public figure{ //定义派生类圆
public,
circle(double a):figure(a,a) { };
void show_area() //虚函数重新 定义,求 圆的面积
{ cout <<―圆的半径 =‖<< x;
cout<<― 圆的面积 =‖<< x*x*3.1416<<endl;
}
};
81
接 2 例 5.22
void main()
{ figure *p; //定义基类的指针 p
triangle t(10.0,6.0); //定义三角形类对象 t
square s(10.0,6.0); //定义矩形类对象 s
circle(10.0); //定义圆类对象 c
p=&t;
p->show_area(); //计算三角形面积
p=&s;
p->show_area(); //计算矩形面积
p=&c;
p->show_area(); //计算圆面积
}
程序运行结果如下,
三角形的高 =10 三角形的底 =6
三角形的面积 =30
矩形的长 =10 矩形的宽 =6
矩形的面积 =60
圆的半径 =10 圆的面积 =314.16
82
5.4.3 纯虚函数和抽象类
纯虚函数是一个在基类中说明的虚函数,它在该基类中没有定义,
但要求在它的派生类中定义自己的版本,或重新说明为纯虚函数。
纯虚函数的一般形式如下,
virtual type func_name(参数表 )=0;
这里,type是函数的返回类型,func_name是函数名,此形式与一
般的虚函数形式基本相同,只是在后面多了” =0‖。假如在例
5.22中,将基类 figure中虚函数 show_area( )写成纯虚函数,格
式如下,
virtual void show_area( )=0;
例 5.23 纯虚函数的例子。
#include<iostream.h>
class circle{
protected,
int r;
public,
void setr(int x) {r=x;}
virtual void show( )=0; //纯虚函数
};
83
接 1 例 5.23
class area:public circle {
public,
void show ( ) //重新定义虚函数 show ( )
{cout<<‖面积是:” << 3.14* r* r<<endl;}
};
class perimeter:public circle{
public,
void show ( ) //重新定义虚函数 show ( )
{cout<<‖周长是:, <<2*3.14*r<<endl;}
};
void main( )
{ circle *ptr;
area ob1; perimeter ob2;
ob1.setr(10); ob2.setr(10);
ptr=&ob1; ptr->show( );
ptr=&ob2; ptr->show( );
}
程序运行结果如下,
面积是,314
周长是,62.8
84
抽象类
如果一个类至少有一个纯虚函数,那么就称该类为 抽象类。
对于抽象类的使用有以下几点规定,
( 1 ) 由于抽象类中至少包含有一个没有定义功能的纯虚函数,因此
抽象类只能用作其它类的基类,不能建立抽象类对象。
( 2 ) 抽象类不能用作参数类型,函数返回类型或显式转换的类型。
但可以声明指向抽象类的指针或引用,此指针可以指向它的派生
类,进而实现多态性。
例如对于抽象类 shape,
class shape{
//…
public,
//…
virtual void rotateshape (int)=0;
virtual void drawshape ( )=0;
virtual void hileteshape( )=0;
};
85
( 3 )
shape s1; //错误,不能建立抽象类的对象
shape *ptr; //正确,可以声明指向抽象类的指针
shape f( ); //错误,抽象类不能作为函数的返回类型
shape g (shape s); //错误,抽象类不能作为函数的参数类型
shape &h(shape &); //正确,可以声明抽象类的引用
( 3 )如果在抽象类的派生类中没有从新说明纯虚函数,而派生类只
是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。
例如对于抽象类 shape,若定义如下的派生类,
class circle, public shape {
int radius,
public,
void rotateshape ( int );
void drawshape ( );
}
由于 shape,,hiliteshape ( ) 是一个纯虚函数,
缺省的 circle,,hiliteshape ( )也是一个纯虚函数,这时 circle
仍为抽象类。
86
5.5 应用举例
例 5.24 应用抽象类,求圆、圆内接正方形和圆外切正方形的面积
和周长。
#include <iostream.h>
class shape { //声明一个抽象类
protected,
double r;
public,
shape (double x) {r=x;}
virtual void area ( )=0; //纯虚函数
virtual void perimeter ( )=0; //纯虚函数
};
class circle, public shape { //声明一个圆派生类
public,
circle (double x):shape (x) { }
void area ( ) //重新定义虚函数 area ( ) 在 circle类中的版本
{ cout<< ―圆面积是:” << 3.14*r*r; }
void perimeter ( )
//重新定义虚函数 perimeter ( ) 在 circle类中的版本
{ cout << ―圆周长是:” <<2*3.14*r<<endl; }
};
87
接 1 例 5.24
class in_square:public shape { //声明一个圆内接正方形
public,
in_square (double x):shape (x) { }
void area ( )
//重新定义虚函数 area ( ) 在 in _square类中的版本
{ cout<< ―内接正方形面积是:,<< 2*r*r; }
void perimeter ( )
//重新定义虚函数 perimeter ( ) 在类 in_square中的版本
{ cout<< ―内接正方形周长是,, << 4*1.414*r<<endl; }
};
88
接 2 例 5.24
class ex_square, public shape { //声明一个圆外切正方形
public,
ex_square (double x):shape(x) { }
void area ( )
//重新定义虚函数 area( ) 在类 ex_square中的版本
{ cout << ―外切正方形面积是:” << 4*r*r; }
void perimeter( )
//重新定义虚函数 perimeter ( ) 在类 ex_square中的版本
{ cout << ―外切正方形周长是:” <<8*r<< endl; }
};
89
接 3 例 5.24
void main ( )
{ shape * ptr; //定义抽象类 shape的指针 ptr
circle ob1 (5);
in_square ob2 (5);
ex_square ob3 (5);
ptr=&ob1; //指针 ptr指向圆类对象 ob1
ptr->area ( ); //求圆的面积
ptr->perimeter ( ); //求圆的周长
ptr=&ob2; //指针 ptr指向圆内接正方形类对象 ob2
ptr->area ( ); //求圆内接正方形的面积
ptr ->perimeter ( ); //求圆内接正方形的周长
ptr =&ob3; //指针 ptr指向圆外切正方形类对象 ob3
ptr->area ( ); //求圆外切正方形的面积
ptr->perimeter ( ); //求圆外切正方形的周长
} 程序运行如下,
圆面积是,78.5 圆周长是,31.4
内接正方形面积是,50 内接正方形周长是, 28.28
外切正方形面积是,100 外切正方形周长是, 40
90
例 5.25 建立了两种类型的表:队列与堆栈。
//建立链表类
#include <iostream.h>
#include <stdlib.h>
#include <ctype.h>
class list{ //声明一个抽象类
public,
list *head; //poiinter to start of list
list *tail; //poiinter to end of list
list *next; //poiinter to next item
int num; //value to be stored
list( ) {head = tail = next = NULL;}
virtual void store (int i) =0; //纯虚函数 store( )
virtual int retrieve ( ) =0; //纯虚函数 retrieve( )
};
91
接 1 例 5.25
//从链表类派生队列类
class queue, public list{
public,
void store (int i );
int retrieve ( );
};
void queue,,store (int i)
//重新定义纯虚函数 store( )在类 queue 中的版本
{ list *item;
item=new queue ;
if (! item)
{ cout<< ―Allocation error \n‖;
exit (1);
}
item->num=i;
//put on end of list
92
接 2 例 5.25
if (tail) tail->next=item;
tail=item;
item->next=NULL;
if (! head) head =tail;
}
int queue,,retrieve ( )
//重新定义纯虚函数 retrieve( )在类 queue 中的版本
{ int i;
list *p;
if (! head )
{ cout<< ―list empty/n‖;
return 0;
} //remove from start of list
i=head->num;
p=head;
head=head->next;
delete p;
return i;
}
93
接 3 例 5.25
//从链表类派生堆栈类
class stack, public list {
public,
void store (int i );
int retrieve ( );
};
void stack,,store (int i )
//重新定义纯虚函数 store( )在类 stack 中的版本
{ list *item;
item=new stack;
if (! item)
{ cout << ―Allocation error/n‖; exit (1); }
item->num=i;
//put on front of list for stac-like operation
if (head ) item->next=head;
head=item;
if (! tail) tail =head;
}
94
接 4 例 5.25
int stack,,retrieve ( )
//重新定义纯虚函数 retrieve( )在类 stack 中的版本
{ int i ;
list *p;
if ( ! head )
{ cout << ―list empty\n‖;
return 0;
}
//remove from start of list
i=head->num;
p=head;
head=head->next;
delete p;
return i;
}
95
接 5 例 5.25
void main ( )
{ list *p;
//demonstrate queue
queue q_ob;
p=&q_ob; //point to queue
p->store(1);
p->store(2);
p->store(3);
cout << ―queue:‖;
cout <<p->retrieve ( );
cout <<p->retrieve ( );
cout <<p->retrieve ( );
cout << ?\n‘;
96
接 6 例 5.25
//demonstrate stack
stack s_ob;
p=&s_ob; //point to stack
p->store(1);
p->store(2);
p->store(3);
cout << ―stack:‖;
cout <<p->retrieve( );
cout <<p->retrieve( );
cout <<p->retrieve( );
cout <<‘\n‘;
}
在上述程序中,声明公共基类 list为抽象类,它是一个为整数之建
立的单向链表类,其中定义了用于向表中保存值的纯虚函数 store( )
和从表中读取值的纯虚函数 retrieve ( )。抽象类 list有两个派生类
queue和 stack,分别表示两种类型的表,队列与堆栈。根据各自的功
能,每个派生类定义了纯虚函数 store ( )和 retrieve( )。
程序运行结果为,
queue:123
stack,321
97
void queue,,store (int i)
//重新定义纯虚函数 store( )在类 queue 中的版本
{ list *item;
item=new queue ;
if (! item)
{ cout<< ―Allocation error \n‖;
exit (1);
}
item->num=i;
//put on end of list
if (tail) tail->next=item;
tail=item;
item->next=NULL;
if (! head) head =tail;
}
98
p=&q_ob
p → head tail next num
store() retrieve()
NULL NULL NULL
p->store(1) item → head tail next num 1
item->num=1; NULL
p->store(2) item → head tail next num 2
item->num=2; NULL
p->store(3) item → head tail next num 3
item->num=3; NULL
99
int queue,,retrieve ( )
//重新定义纯虚函数 retrieve( )在类 queue 中的版本
{ int i;
list *p;
if (! head )
{ cout<< ―list empty/n‖;
return 0;
} //remove from start of list
i=head->num;
p=head;
head=head->next;
delete p;
return i;
}
100
list *p
p → head tail next num
store() retrieve()
NULL NULL NULL
p->retrieve() item → head tail next num 1
item->num=1; NULL
p->retrieve() item → head tail next num 2
item->num=2; NULL
p->retrieve() item → head tail next num 3
item->num=3; NULL
p→
101
void stack,,store (int i )
//重新定义纯虚函数 store( )在类 stack 中的版本
{ list *item;
item=new stack;
if (! item)
{ cout << ―Allocation error/n‖;
exit (1);
}
item->num=i;
//put on front of list for stac-like operation
if (head ) item->next=head;
head=item;
if (! tail) tail =head;
}
102
p=&s_ob
p → head tail next num
store() retrieve()
NULL NULL NULL
p->store(1) item → head tail next num 1
item->num=1; NULL
p->store(2) item → head tail next num 2
item->num=2;
p->store(3) item → head tail next num 3
item->num=3;
103
int stack,,retrieve ( )
//重新定义纯虚函数 retrieve( )在类 stack 中的版本
{ int i ;
list *p;
if ( ! head )
{ cout << ―list empty\n‖;
return 0;
}
//remove from start of list
i=head->num;
p=head;
head=head->next;
delete p;
return i;
}
104
list *p
p → head tail next num
store() retrieve()
NULL NULL NULL
p->retrieve() item → head tail next num 1
item->num=1; NULL
p->retrieve() item → head tail next num 2
item->num=2;
p->retrieve() item → head tail next num 3
item->num=3;
p→
105
例 5.26:异质链表实现,有三个类 student,teacher,
staff,再定义一个链表类,此类用来存放这几个不同类
的对象,并将链表类 list 声明为所有这些类的友元,
使它们可以访问它们的私有成员。
106
#include <iostream.h>
class person{ //定义一个共同的基类,它具有公共的数据成员
friend class list; //链表类作为本类的友元
protected,
char name[20]; //定义姓名
int age; //定义年龄
char add[40]; //定义地址
char tele[20]; //定义电话号码
static person *ptr; //定义一个指向 person类对象的
静态指针 (所有本类的对象共享,单独存放的全局变量 )。
person *next; //指向下一个对象的指针
public,
person(char *name,int age,char *add,char *tele);//
构造函数
virtual void print(); //说明虚函数
virtual void insert(){};//定义虚函数并且什么也不做
,只定义一个接口
};
107
class student:public person{ //派生类 student
friend class list; //链表类作为本类的友元
int level; //定义年级
float grade_point_average; //定义平均分
public,
student(char *name,int age,char *add,char *tele,
int level,float grade_point_average);
//声明构造函数
void print(); //重新定义 print()函数
void insert(); //重新定义 insert()函数
};
108
class teacher:public person{ //派生类 teacher
friend class list; //链表类作为本类的友元
float salary; //定义工资
public,
teacher(char *name,int age,char *add,
char *tele,float salary);
//声明构造函数
void print(); //重新定义 print()函数
void insert(); //重新定义 insert()函数
};
109
class staff:public person{ //派生类 staff
friend class list; //链表类作为本类的友元
float hourly_wages; //定义计时工资
public,
staff(char *name,int age,char *add,char *tele,
float hourly_wages);
//声明构造函数
void print(); //重新定义 print()函数
void insert(); //重新定义 insert()函数
};
110
class list{ //定义异质链表类
person *root; //链表头指针
public,
list(){ root=0; } //链表构造函数,初始为 0
void insert_person(person *node); //向链表插入一个
对象结点
void remove(char *name); //从链表移去一个
对象结点
void print_list(); //输出整个链表
};
111
person::person(char *name,int age,char *add,char *tele)
{ //person 的构造函数
strcpy(person::name,name);
strcpy(person::add,add);
strcpy(person::tele,tele);
person::age=age;
next=0;
}
void person::print() //基类的虚成员函数 print()版本,输
出基类数据成员
{
cout<<"\n name,"<<name<<"\n";
cout<<"age,"<<age<<"\n";
cout<<"address,"<<add<<"\n";
cout<<"telephone number,"<<tele<<"\n";
}
112
student::student(char *name,int age,char *add,char *tele,
int level,float grade_point_average)
,person(name,age,add,tele)
{ //student 派生类的构造函数,需缀上基类的构造函数
student::level=level;
student::grade_point_average=grade_point_average;
}
void student::print() //派生类 student的成员函数 print()
新版本
{
person::print();
cout<<"grade point
average:"<<grade_point_average<<"\n";
}
void student::insert()
{ //将 student 类的一个对象赋给 ptr 基类指针,这是允许的
ptr=new
student(name,age,add,tele,level,grade_point_average);
113
teacher::teacher(char *name,int age,char *add,char
*tele,float salary):person(name,age,add,tele)
//teacher 派生类的构造函数,需缀上基类的构造函数
{
teacher::salary=salary;
}
void teacher::print() //派生类 teacher的成员函数 print()
新版本
{
person::print();
cout<<"salary,"<<salary<<"\n";
}
void teacher::insert()
{ //将 teacher类的一个对象赋给 ptr 基类指针,这是允许的
ptr=new teacher(name,age,add,tele,salary);
}
114
staff::staff(char *name,int age,char *add,char *tele,
float hourly_wages)
,person(name,age,add,tele) //staff 派生类
的构造函数,需缀上基类的构造函数
{
staff::hourly_wages=hourly_wages;
}
void staff::print() //派生类 staff的成员函数
print()新版本
{
person::print();
cout<<"hourly_wages,"<<hourly_wages<<"\n";
}
void staff::insert()
{ //将 staff类的一个对象赋给 ptr 基类指针,这是允许的
ptr=new staff(name,age,add,tele,hourly_wages);
}
115
void list::insert_person(person *node)
{ //向异质链表插入一个对象,可能是 student或 teacher或 staff
char key[20];
strcpy(key,node->name); //将需插入的对象的姓名赋给 key
person *curr_node=root;
person *previous=0; //定义二个查找指针
while(curr_node !=0 && strcmp(curr_node->name,key)<0)
{ //用 curr_node在前,previous在后及 key 循环查找插
入位置,由 name 字符串升序排序,strcmp(串 1,串 2)<0 表示串
1<串 2,继续找
previous=curr_node;
curr_node=curr_node->next;
}
node->insert(); //由于 insert() 是虚函数,它会根据
node 指针的对象调用不同的版本,ptr=new <三个派生类名之一
>(数据成员变量表 ),而数据成员变量是由构造函数赋值的,由
new 特性,int *s; s=new int(20); 会开辟空间并把值 20给空
间,因此说:调用虚函数将待插入对象赋给 ptr所指向的单元。
116
node->ptr->next=curr_node;
if(previous==0)
root=node->ptr;
else
previous->next=node->ptr;
//以上三句是把 ptr 指的结点插入链中,这里,node->ptr,
previous->ptr,curr_node->ptr 统统都是一样的,即相当于
person::ptr 说明 ptr 受限的对象是 stu::ptr或 tea::ptr或
sta::ptr,因此 node->ptr->next 可写成 (node->ptr)->next
或 person::ptr->next 但不能把 node->name 写成
person::name,它只能出现在构造函数中。
}
117
void list::remove(char *name)
{ //从异质链表中移去一对象,此对象用关键字 name 去匹配
person *curr_node=root;
person *previous=0;
while(curr_node !=0 && strcmp(curr_node->name,name)!=0)
{ previous=curr_node; //用循环寻找此对象
curr_node=curr_node->next;
}
if(curr_node!=0 && previous==0)//若此对象为链表中的头结点
{ root=curr_node->next;
delete curr_node;
}
else if(curr_node!=0 && previous!=0) //此对象为链表中的
非头结点
{ previous->next=curr_node->next;
delete curr_node;
}
}
118
void list::print_list()
{
person *cur=root; //先将头指针赋给 cur
while(cur!=0) //循环输出结点内容并移动指针
{
cur->print(); //根据 cur 所指向的对象不同调用不
同的 print() 版本
cur=cur->next;
}
}
119
person *person::ptr=0; //为静态变量赋初值
void main()
{
char c;
list people; //定义 list 类的对象 people,同时调用
// list 构造函数,也即 root=0;
student stu("刘影 liuying",20,"上海 shanghai","03578395-
456",3,80.0);
//定义 student 类的对象 stu,同时调用
// student 构造函数,也即先基类 person
// 构造函数,再自己
teacher tea("李明 liming",35,"北京 beijing","0105918695-
106",560.50);
staff sta("陈林 chenling",40,"青岛 qingdao","05325895
944-335",10);
120
people.insert_person(&stu);
people.insert_person(&tea);
people.insert_person(&sta);
cout<<"插入结束 !\n";
people.print_list();
cout<<"\n以上打印出原链表 !\n";
cin>>c;
people.remove("陈林 chenling");
people.remove("李明 liming");
people.remove("王迢 wangchong");
cout<<"删除结束 !\n";
people.print_list();
cout<<"\n以上打印出删除后链表 !\n";
}