第 8章 多态性第 8章 多态性
8.1 多态性概述
8.2 运算符重载
8.3 虚函数
8.4 抽象类第 8章 多态性
8.1 多态性概述所谓多态性是指同一个接口可以通过多种方法调用,如图 8-1所示。通俗地说,多态性是指用一个相同的名字定义不同的函数,这些函数的执行过程不同,
但是有相似的操作,即用同样的接口访问不同的函数。
比如,一个对象中有很多求两个数中最大值的行为,
虽然可以针对不同的数据类型,写很多不同名称的函数来实现,但事实上,它们的功能几乎完全相同。这时,就可以利用多态的特征,用统一的标识来完成这些功能。
第 8章 多态性图 8-1 多态性为用户提供单一接口示意图第 8章 多态性面向对象的多态性从实现的角度来讲,可以分为静态多态性和动态多态性两种 。 静态多态性是在编译的过程中确定同名操作的具体操作对象的,而动态多态性则是在程序运行过程中动态地确定操作所针对的具体对象的。这种确定操作具体对象的过程就是联编 (binding),也称为绑定。联编是指计算机程序自身彼此关联的过程。也就是把一个标识符名和一个存储地址联系在一起的过程。用面向对象的术语讲,就是把一条消息和一个对象的方法相结合的过程。
第 8章 多态性所谓消息,是指对类的成员函数的调用。不同的方法是指不同的实现,也就是调用了不同的函数。按照联编进行阶段的不同,联编方法可以分为两种:静态联编和动态联编。这两种联编过程分别对应着多态的两种实现方式。联编工作在编译连接阶段完成的情况称为静态联编。在编译、连接过程中,系统就可以根据类型匹配等特征确定程序中操作调用与执行该操作的代码的关系,即确定某一个同名标识到底是要调用哪一段程序代码。函数重载和运算符重载就属于静态多态性。
第 8章 多态性和静态联编相对应,如果联编工作在程序运行阶段完成,则称为动态联编 。 在编译,连接过程中无法解决的联编问题,要等到程序开始运行之后再来确定 。 例如,
本章将要介绍的虚函数就是通过动态联编完成的 。
函数重载在函数及类的章节中曾做过详细的讨论,
所以在本章中,静态多态性主要介绍运算符重载;对于动态多态性,将对虚函数作详细介绍。
第 8章 多态性
8.2 运算符重载
C++中预定义的运算符的操作对象只能是基本数据类型 。 实际上,对于很多用户自定义的类型 (如类 ),也需要有类似的运算操作 。 例如,下面的程序声明了一个点类 point。
classpoint //point类声明
{
private:
intx,y;
第 8章 多态性
public,//构造函数
point(intxx=0,intyy=0){x=xx;y=yy;}
intget_x(); //显示 x值
intget_y(); //显示 y值
//...
};
第 8章 多态性于是我们可以这样声明点类的对象:
pointp1(1,1),p2(3,3)
如果我们需要对 p1和 p2进行加法运算,该如何实现呢? 我们当然希望能使用,+”运算符,写出表达式
,p1+p2”,但是编译的时候却会出错,因为编译器不知道该如何完成这个加法 。 这时候,我们就需要自己编写程序来说明,+”在作用于 point类对象时,该实现什么样的功能,这就是运算符重载 。 运算符重载是对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据时,导致不同类型的行为 。
第 8章 多态性在运算符重载的实现过程中,首先把指定的运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参,然后,根据实参的类型来确定需要调用的函数。这个过程是在编译过程中完成的。
第 8章 多态性
8.2.1 运算符重载的规则运算符是在 C++系统内部定义的,它们具有特定的语法规则,如参数说明,运算顺序,优先级别等 。 因此,运算符重载时必须要遵守一定的规则 。
① C++中的运算符除了少数几个 ( 类属关系运算符
,.”,作用域分辨符,,:”,成员指针运算符,*”,
sizeof运算符和三目运算符,?,,) 之外,全部可以重载,而且只能重载 C++中已有的运算符,不能臆造新的运算符 。
第 8章 多态性
② 重载之后运算符的优先级和结合性都不能改变,
也不能改变运算符的语法结构,即单目运算符只能重载为单目运算符,双目运算符只能重载为双目运算符 。
③ 运算符重载后的功能应当与原有功能相类似 。
④ 重载运算符含义必须清楚,不能有二义性 。
运算符的重载形式有两种:重载为类的成员函数和重载为类的友元函数 。
第 8章 多态性运算符重载为类的成员函数的一般语法形式如下:
<函数类型 >operator<运算符 >(形参表 )
{
函数体;
}
运算符重载为类的友元函数的一般语法形式如下:
friend<函数类型 >operator<运算符 >(形参表 )
{
函数体;
}
第 8章 多态性其中:
① 函数类型指定了重载运算符的返回值类型,也就是运算结果类型 。
② operator是定义运算符重载函数的关键字 。
③ 运算符是要重载的运算符名称 。
④ 形参表给出重载运算符所需要的参数和类型 。
⑤ friend是对于运算符重载为友元函数时,在函数类型说明之前使用的关键字。
第 8章 多态性特别需要注意的是,当运算符重载为类的成员函数时,函数的参数个数比原来的操作数个数要少一个
(后置,++”、,--”除外 );当重载为类的友元函数时,
参数个数与原操作数的个数相同。原因是重载为类的成员函数时,如果某个对象使用重载了的成员函数,
自身的数据可以直接访问,就不需要再放在参数表中进行传递,少了的操作数就是该对象本身。
第 8章 多态性
8.2.2 运算符重载为成员函数运算符重载实质上就是函数重载,当运算符重载为成员函数之后,它就可以自由地访问本类的数据成员了。实际使用时,总是通过该类的某个对象来访问重载的运算符。如果是双目运算符,一个操作数是对象本身的数据,由 this指针指出,另一个操作数则需要通过运算符重载函数的参数表来传递;如果是单目运算符,操作数由对象的 this指针给出,就不再需要任何参数。下面分别介绍这两种情况。
第 8章 多态性
1,双目运算,oprdlBoprd2
对于双目运算符 B,如果要重载 B为类的成员函数,
使之能够实现表达式 oprdlBoprd2(其中 oprdl为 A类的对象 ),则应当把 B重载为 A类的成员函数,该函数只有一个形参,形参的类型是 oprd2所属的类型。经过重载之后,表达式 oprdlBoprd2就相当于函数调用
oprdl.operatorB(oprd2)。
第 8章 多态性
2,单目运算
1)前置单目运算,Uoprd
对于前置单目运算符 U,如,-”(负号 ),,++”等,
如果要重载 U为类的成员函数,用来实现表达式
Uoprd(其中 oprd为 A类的对象 ),则 U应当重载为 A类的成员函数,函数没有形参 。 经过重载之后,表达式 Uoprd
相当于函数调用 oprd.operatorU()。
例如,前置单目运算符,++”重载的语法形式如下:
<函数类型 >operator++();
使用前置单目运算符,++”的语法形式如下:
++<对象 >;
第 8章 多态性
2) 后置单目运算,oprdV
再来看后置运算符 V,如,++”和,--”,如果要将它们重载为类的成员函数,用来实现表达式 oprd++或
oprd-(其中 oprd为 A类的对象 ),那么运算符就应当重载为 A类的成员函数,这时函数要带有一个整型 (int)形参 。
重载之后,表达式 oprd++和 oprd--就相当于函数调用
oprd.operator++(0)和 oprd.operator--(0)。
例如,后置单目运算符,++”重载的语法形式如下:
<函数类型 >operator++(int);
使用后置单目运算符,++”的语法形式如下:
<对象 >++;
第 8章 多态性
【 例 8-1】 双目运算符重载为成员函数例题 。
本例题重载二维点 point加减法运算 (关于二维点
point类的定义在前面章节中已介绍过 ),将一个双目运算符重载为成员函数 。 point的加减法是 x和 y分别相加减,运算符的两个操作数都是 point类的对象,因此,
可以把,+”,,-”运算符重载为 point类的成员函数,
重载函数只有一个形参,类型同样也是 point类对象 。
第 8章 多态性
#include<iostream.h>
classpoint
{
private:
floatx,y;
public:
point(floatxx=0,floatyy=0){x=xx;y=yy;}
floatget_x(){returnx;}
floatget_y(){returny;}
第 8章 多态性
pointoperator+(pointp1); //重载运算符,+”
pointoperator-(pointp1); //和,-”为成员函数
};
pointpoint::operator+(pointq)
{
returnpoint(x+q.x,y+q.y);
}
pointpoint::operator-(pointq)
{
returnpoint(x-q.x,y-q.y);
}
voidmain()
第 8章 多态性
{
pointp1(3,3),p2(2,2),p3,p4; //声明 point类的对象
p3=p1+p2; //两点相加
p4=p1-p2; //两点相减
cout<<"p1+p2:x="<<p3.get_x()<<",y="<<p3.get_y()<<endl;
cout<<"p1-p2:x="<<p4.get_x()<<",y="<<p4.get_y()<<endl;
}
第 8章 多态性在本例中,将 point的加减法运算重载为 point类的成员函数 。 可以看出,除了在函数声明及实现的时候使用了关键字 operator之外,运算符重载成员函数与类的普通成员函数没有什么区别 。 在使用的时候,可以直接通过运算符,操作数的方式来完成函数调用 。 这时,运算符,+”,,-”原有的功能都不改变,对整型数,浮点数等基本类型数据的运算仍然遵循 C++预定义的规则,同时添加了新的针对 point运算的功能 。,+”
这个运算符,作用于不同的对象就会导致完全不同的操作行为,具有了更广泛的多态特征 。
第 8章 多态性本例中重载的,+”,,-”函数中,都是创建一个临时的无名对象作为返回值:
returnpoint(x+q.x,y+q.y);
这表面上看起来像是对构造函数的调用,但其实并非如此 。 这是临时对象语法,它的含义是创建一个临时对象并返回它 。 当然,也可以按如下形式返回函数值:
pointpoint::operator+(pointq)
{
pointp;
p.x=x+q.x;
第 8章 多态性
p.y=y+q.y;
returnp;
}
pointpoint::operator-(pointq)
{
pointp;
p.x=x-q.x;
p.y=y-q.y;
returnp;
}
第 8章 多态性这两种方法的执行效率是完全不同的 。 后者的执行过程是这样的:创建一个局部对象 p(这时会调用构造函数 ),执行 return语句时,会调用拷贝构造函数,将
p的值拷贝到主调函数中的一个无名临时对象中 。 当函数 operator+结束时,会调用析构函数析构对象 p,然后
p消亡 。 两种方法相比,前一种方法的效率高,因为它是直接将一个无名临时对象创建到主调函数中 。
例 8-1的程序运行结果为
p1+p2:x=5,y=5
p1-p2:x=1,y=1
第 8章 多态性
【 例 8-2】 单目运算符重载为成员函数例题 。
本程序为时钟计时程序 。 在程序中将单目运算符重载为类的成员函数,单目运算符前置,++”和后置
,++”的操作数是时钟类的对象,可以把这些运算符重载为时钟类的成员函数 。 对于前置单目运算符,重载函数没有形参;对于后置单目运算符,重载函数有一个整数形参 。 本例中,我们把自增前置,++”和自减前置,--”运算重载为 point类的成员函数 。
第 8章 多态性
#include<iostream.h>
classpoint
{
private:
floatx,y;
public:
point(floatxx=0,floatyy=0){x=xx;y=yy;}
floatget_x(){returnx;}
floatget_y(){returny;}
第 8章 多态性
pointoperator++(); //重载前置运算符,++”
pointoperator--(); //重载前置运算符,--”
};
pointpoint::operator++()
{
if(x<640)++x;
if(y<480)++y;
return*this;
}
第 8章 多态性
pointpoint::operator--()
{
if(x>0)--x;
if(y>0)--y;
return*this;
}
voidmain()
{
pointp1(10,10),p2(200,200); //声明 point类的对象
for(inti=0;i<5;i++)
{
第 8章 多态性
cout<<"p1:x="<<p1.get_x()<<",y="<<p1.get_y()<<endl;
++p1;
}
for(i=0;i<5;i++)
{
cout<<"p2:x="<<p2.get_x()<<",y="<<p2.get_y()<<endl;
--p2;
}
}
第 8章 多态性程序运行结果为
p1:x=10,y=10
p1:x=11,y=11
p1:x=12,y=12
p1:x=13,y=13
p1:x=14,y=14
p2:x=200,y=200
p2:x=199,y=199
p2:x=198,y=198
p2:x=197,y=197
p2:x=196,y=196
第 8章 多态性前置单目运算符和后置单目运算符重载的最主要的区别就在于重载函数的形参 。 语法规定,前置单目运算符重载为成员函数时没有形参,而后置单目运算符重载为成员函数时,需要有一个 int型形参 。 这个 int
型参数在函数体中并不使用,纯粹是用来区别前置与后置,因此,参数表中可以只给出类型名,没有参数名 。
第 8章 多态性
8.2.3 运算符重载为友元函数运算符也可以重载为类的友元函数,这样它就可以自由地访问该类的任何数据成员 。 这时,运算所需要的操作数都需要通过函数的形参表来传递,在参数表中形参从左到右的顺序就是运算符操作数的顺序 。
但是,有些运算符不能重载为友元,如,=”、
,( ),,,[]”和,->”。
第 8章 多态性
1,双目运算,oprdlBoprd2
对于双目运算符 B,如果 oprdl为 A类的对象,则应当把 B重载为 A类的友元函数,该函数有两个形参,其中一个形参的类型是 A类。经过重载之后,表达式
oprdlBoprd2就相当于函数调用 operatorB(oprdl,oprd2)。
第 8章 多态性
2,单目运算
1)前置单目运算,Uoprd
对于前置单目运算符 U,如,-”(负号 )等,如果要实现表达式 Uoprd(其中 oprd为 A类的对象 ),则 U可以重载为 A类的友元函数,函数的形参为 A类的对象 。 经过重载之后,表达式 Uoprd 相 当 于 函 数 调 用
operatorU(oprd)。
第 8章 多态性
2)后置单目运算,oprdV
对于后置运算符 V,如,++”和,--”,如果要实现表达式 oprd++或 oprd--( 其中 oprd为 A类的对象 ),
那么运算符就可以重载为 A类的友元函数,这时函数的形参有两个,一个是 A类的对象 oprd,另一个是整型
(int)形参 。 重载之后,表达式 oprd++和 oprd--就相当于函数调用 operator++(oprd,0)和 operator--(oprd,0)。
第 8章 多态性
【 例 8-3】 双目运算符重载为友元重载例题 。
本例题用运算符重载为友元函数的方法重做两点加减法运算 。
#include<iostream.h>
classpoint
{
private:
floatx,y;
public:
第 8章 多态性
point(floatxx=0,floatyy=0){x=xx;y=yy;}
floatget_x(){returnx;}
floatget_y(){returny;}
friendpointoperator+(pointp1,pointp2); // 重 载 运 算 符
,+”
friendpointoperator-(pointp1,pointp2); //和,-”为友元函数
};
pointoperator+(pointp1,pointp2)
{
第 8章 多态性
returnpoint(p1.x+p2.x,p1.y+p2.y);
}
pointoperator-(pointp1,pointp2)
{
returnpoint(p1.x-p2.x,p1.y-p2.y);
}
voidmain()
{
第 8章 多态性
pointp1(3,3),p2(2,2),p3,p4; //声明 point类的对象
p3=p1+p2; //两点相加
p4=p1-p2; //两点相减
cout<<"p1+p2:x="<<p3.get_x()<<",y="<<p3.get_y()<<endl;
cout<<"p1-p2:x="<<p4.get_x()<<",y="<<p4.get_y()<<endl;
}
从上述程序可以看到,将运算符重载为类的友元函数时,必须把操作数全部通过形参的方式传递给运算符重载函数 。 和例 8-1相比,本例题的主函数根本没有做任何改动,主要的变化在 point类的成员,程序运行的结果完全相同 。
第 8章 多态性
8.2.4 其它运算符重载前面介绍了一些简单运算符的重载,除此之外,还有以下运算符也常被重载 。
① 比较运算符重载 ( 如 <,>,<=,>=,==,!=) 。
② 赋值运算符重载 ( 如 =,+=,-=,*=,/=) 。
③ 下标运算符,[]”重载 。
下标运算符,[]”通常用于取数组中的某个元素,
通过下标运算符重载,可以实现数组下标的越界检测等 。
第 8章 多态性
④ 运算符 new和 delete重载 。
通过重载 new和 delete,可以克服 new和 delete的不足,
使其按要求完成对内存的管理 。
⑤ 逗号运算符,,,重载 。
逗号运算符是一个双目运算符,和其它运算符一样,
我们也可以通过重载逗号运算符来达到期望的结果 。
逗号运算符构成的表达式为,左操作数,右操作数,,
该表达式返回右操作数的值 。
第 8章 多态性
8.3 虚函数
8.3.1为什么要引入虚函数为什么要引入虚函数,我们来看一个例子 。
【 例 8-4】 没有使用虚函数的例题 。
#include<iostream.h>
classbase //定义基类 base
{
public:
第 8章 多态性
voidwho()
{cout<<"thisistheclassofbase!"<<endl;}
};
classderive1:publicbase //定义派生类 derive1
{
public:
voidwho()
{cout<<"thisistheclassofderive1!"<<endl;}
};
classderive2:publicbase //定义派生类 derive2
第 8章 多态性
public:
voidwho()
{cout<<"thisistheclassofderive2!"<<endl;}
};
main()
{
baseobj,*ptr;
derive1obj1;
derive2obj2;
ptr=&obj;
ptr->who();
第 8章 多态性
ptr=&obj1;
ptr->who();
ptr=&obj2;
ptr->who();
obj1.who();
obj2.who();
return1;
}
此例在 main()函数中定义了一个基类对象 obj,和两个派生类对象 obj1与 obj2,又定义了一个指向基类对象的指针 ptr。
第 8章 多态性此程序的意图是用 ptr指针分别指向不同的对象,
以便执行不同对象所对应的类的成员函数 。 当 ptr指向
obj对象时,ptr->who()调用 base类的成员函数 who();
当 ptr指向 obj1对象时,我们希望 ptr->who()调用 derive1
类的成员函数 who();而当 ptr指向 obj2对象时,则希望
ptr->who()调用 derive2类的成员函数 who()。 此程序执行后实际得到的结果为第 8章 多态性
thisistheclassofbase! (a)
thisistheclassofbase! (b)
thisistheclassofbase! (c)
thisistheclassofderive1! (d)
thisistheclassofderive2! (e)
第 8章 多态性在运行结果中,(a),(d)和 (e)与所预想的相符,而
(b)和 (c)却不是希望得到的。这说明,不管指针 ptr当前指向哪个对象 (是基类对象还是派生类对象 ),ptr->who()
调用的都是基类中定义的 who()函数。也就是说,通过指针引起的普通成员函数调用,仅仅与指针的类型有关,而与指针正指向什么对象无关。在这种情况下,
必须采用显式的方式调用派生类的函数成员。
第 8章 多态性例如:
obj1.who()或 obj2.who()
或者是采用对指针的强制类型转换的方法,例如:
((derive1*)ptr)->who()或 ((derive2*)ptr)->who()
本来使用对象指针是为了表达一种动态的性质,
即当指针指向不同对象时执行不同的操作,现在看来并没有起到这种作用。要实现这种功能,就需要引入虚函数的概念。这里,只需将基类的 who()函数声明为虚函数即可。
第 8章 多态性
8.3.2 虚函数的定义及使用
1.虚函数的定义虚函数的定义是在基类中进行的 。 它是在基类中需要定义为虚函数的成员函数的声明中冠以关键字
virtual。 当基类中的某个成员函数被声明为虚函数后,
此虚函数就可以在一个或多个派生类中被重新定义,
在派生类中重新定义时,其函数原型,包括返回类型,
函数名,参数个数,参数类型以及参数的顺序都必须与基类中的原型完全相同 。
第 8章 多态性一般虚函数的定义语法如下:
virtual<函数类型 ><函数名 >(形参表 )
{
函数体
}
其中,被关键字 virtual说明的函数为虚函数 。 特别要注意的是,虚函数的声明只能出现在类声明中的函数原型声明中,而不能出现在成员的函数体实现的时候 。
第 8章 多态性需要注意,动态联编只能通过成员函数来调用或者通过指针,引用来访问虚函数 。 如果使用对象名的形式访问虚函数,则将采用静态联编方式调用虚函数,
而无需在运行过程中进行调用 。 下面是通过指针访问虚函数的例题 。
第 8章 多态性
【 例 8-5】 使用虚函数例题 。
#include<iostream.h>
classbase //定义基类 base
{
public:
virtualvoidwho() //虚函数声明
{cout<<"thisistheclassofbase!"<<endl;}
};
第 8章 多态性
classderive1:publicbase //定义基类派生类 derive1
{
public:
voidwho() //重新定义虚函数
{cout<<"thisistheclassofderive1!"<<endl;}
};
classderive2:publicbase //定义派生类 derive2
{
public:
第 8章 多态性
voidwho() //重新定义虚函数
{cout<<"thisistheclassofderive2!"<<endl;}
};
main()
{
baseobj,*ptr;
derive1obj1;
derive2obj2;
ptr=&obj;
ptr->who();
第 8章 多态性
ptr=&obj1;
ptr->who();
ptr=&obj2;
ptr->who();
return1;
}
此时,程序运行结果为
thisistheclassofbase!
thisistheclassofderive1!
thisistheclassofderive2!
第 8章 多态性分析一下上面这个程序,在基类中对 voidwho()进行了虚函数声明,这样,在其派生类中就可以重新定义它。在派生类 derive1和 derive2中分别重新定义
voidwho()函数,注意,此虚函数在派生类中重新定义时不再需要 virtual声明,此声明只在其基类中出现一次。
在 voidwho()函数被重新定义时,其函数的原型与基类中的函数原型必须完全相同。
第 8章 多态性在 main()函数中,定义了一个指向基类类型的指针,它也被允许指向其派生类 。 在执行过程中,不断改变它所指向的对象,ptr->who()就能调用不同的版本 。 虽然都是 ptr->who()语句,但是,当 ptr指向不同的对象时,所对应的执行动作就不同 。 由此可见,用虚函数充分体现了多态性 。 并且,因为 ptr指针指向哪个对象是在执行过程中确定的,所以体现的又是一种动态的多态性 。
第 8章 多态性
2,虚函数与重载的关系在一个派生类中重新定义基类的虚函数是函数重载的另一种特殊形式,但它不同于一般的函数重载 。
一般的函数重载,只要函数名相同即可,函数的返回类型及所带的参数可以不同 。 但当重载一个虚函数时,
也就是说在派生类中重新定义此虚函数时,要求函数名,
返回类型,参数个数,参数类型以及参数的顺序都与基类中的原型完全相同,不能有任何的不同 。
第 8章 多态性
3,多继承中的虚函数在多继承中由于派生类是由多个基类派生而来的,
因此,虚函数的使用就不像单继承那样简单 。 请看下面的例题 。
【 例 8-6】 多继承中使用虚函数例题 。
#include<iostream.h>
classbase1 //定义基类 base1
{
public:
virtualvoidwho() //函数 who()为虚函数第 8章 多态性
{cout<<"thisistheclassofbase1!"<<endl;}
};
classbase2 //定义基类 base2
{
public:
voidwho() //此函数 who()为一般的函数
{cout<<"thisistheclassofbase2!"<<endl;}
};
classderive:publicbase1,publicbase2
{
第 8章 多态性
public:
voidwho()
{cout<<"thisistheclassofderive!"<<endl;}
};
main()
{
base1obj1,*ptr1;
base2obj2,*ptr2;
deriveobj3;
第 8章 多态性
ptr1=&obj1;
ptr1->who();
ptr2=&obj2;
ptr2->who();
ptr1=&obj3;
ptr1->who();
ptr2=&obj3;
ptr2->who();
return1;
}
第 8章 多态性此时,程序执行的结果为
thisistheclassofbase1!
thisistheclassofbase2!
thisistheclassofderive!
thisistheclassofbase2!
从上面的例子看出,派生类 derive中的函数 who()在不同的场合呈现不同的性质 。 如相对 base1路径,由于在
base1中的 who()函数前有关键字 virtual,所以它是一个虚函数;若相对于 base2派生路径,在 base2中的 who()函数为一般函数,所以,此时它只是一个重载函数 。
第 8章 多态性当 base1类指针指向 derive类对象 obj3时,函数 who()
就呈现出虚特性;当 base2类指针指向 derive类对象 obj3
时,函数只呈现一般的重载特性 。
若一个派生类,它的多个基类中有公共的基类,
在公共基类中定义一个虚函数,则多重派生以后仍可以重新定义虚函数,也就是说,虚特性是可以传递的。
请看下面的例题。
第 8章 多态性
【 例 8-7】 多继承中虚特性的传递例题 。
#include<iostream.h>
classbase
//定义基类 base
{
public:
virtualvoidwho() //定义虚函数
{cout<<"thisistheclassofbase!"<<endl;}
};
classbase1:publicbase //定义派生类 base1
第 8章 多态性
{
public:
voidwho()
{cout<<"thisistheclassofbase1!"<<endl;}
};
classbase2:publicbase //定义派生类类 base2
{
public:
voidwho()
{cout<<"thisistheclassofbase2!"<<endl;}
};
第 8章 多态性
classderive:publicbase1,publicbase2 //定义派生类 derive
{
public:
voidwho()
{cout<<"thisistheclassofderive!"<<endl;}
};
main()
{
base1*ptr1;
base2*ptr2;
第 8章 多态性
deriveobj;
ptr1=&obj;
ptr1->who();
ptr2=&obj;
ptr2->who();
return1;
}
此时,程序执行的结果为
thisistheclassofderive!
thisistheclassofderive!
第 8章 多态性从本例题可以看出,虚特性是可以传递的。 base类作为
base1和 base2类的直接基类,它的成员函数 who()被声明为虚函数,则 base1和 base2类中的 who()都具有虚特性,
即均为虚函数;而 derive类为 base1和 base2类的派生类,
因此,它的成员函数 who()也为虚函数。
第 8章 多态性
8.3.3 虚函数的限制如果我们将所有的成员函数都设置为虚函数,当然是很有益的 。 它除了会增加一些额外的资源开销,
没有什么坏处 。 但设置虚函数须注意以下几点 。
① 只有成员函数才能声明为虚函数 。 因为虚函数仅适用于有继承关系的类对象,所以普通函数不能声明为虚函数 。
第 8章 多态性
② 虚函数必须是非静态成员函数 。 这是因为静态成员函数不受限于某个对象 。
③ 内联函数不能声明为虚函数 。 因为内联函数不能在运行中动态确定其位置 。
④构造函数不能声明为虚函数。多态是指不同的对象对同一消息有不同的行为特性。虚函数作为运行过程中多态的基础,主要是针对对象的,而构造函数是在对象产生之前运行的,因此,虚构造函数是没有意义的。
第 8章 多态性
⑤ 析构函数可以声明为虚函数 。 析构函数的功能是在该类对象消亡之前进行一些必要的清理工作 。 析构函数没有类型,也没有参数,和普通成员函数相比,
虚析构函数情况略为简单些 。
第 8章 多态性虚析构函数的声明语法如下:
virtual~类名例如:
classB
{
public:
//…
virtual~B();
};
第 8章 多态性
8.4 抽象类
8.4.1 纯虚函数一个抽象类至少带有一个纯虚函数 。 纯虚函数是一个在基类中说明的虚函数,它在该基类中没有定义具体的操作内容,要求各派生类根据实际需要定义自己的实现内容 。 纯虚函数的声明形式如下:
virtual<函数类型 ><函数名 >(参数表 )=0
纯虚函数与一般虚函数在书写形式上的不同在于其后面加了,=0”,表明在基类中不用定义该函数,它的实现部分 —— 函数体留给派生类去做。
第 8章 多态性
8.4.2 抽象类抽象类的主要作用是通过它为一个类族建立一个公共的接口,使它们能够更有效地发挥多态特性 。 使用抽象类时需注意以下几点 。
① 抽象类只能用作其它类的基类,不能建立抽象类对象 。 抽象类处于继承层次结构的较上层,一个抽象类自身无法实例化,而只能通过继承机制,生成抽象类的非抽象派生类,然后再实例化 。
第 8章 多态性
② 抽象类不能用作参数类型,函数返回值或显式转换的类型 。
③ 可以声明一个抽象类的指针和引用 。 通过指针或引用,我们就可以指向并访问派生类对象,以访问派生类的成员 。
抽象类派生出新的类之后,如果派生类给出所有纯虚函数的函数实现,这个派生类就可以声明自己的对象,因而不再是抽象类;反之,如果派生类没有给出全部纯虚函数的实现,这时的派生类仍然是一个抽象类 。
第 8章 多态性
【 例 8-8】 抽象类例题 。
我们来看这个例题 。 在基 类 Shapes中 将成员
display()声明为纯虚函数,这样,基类 Shapes就是一个抽象类,我们无法声明 Shapes类的对象,但是可以声明 Shapes类的指针和引用 。 Shapes类经过公有派生产生了 Rectangle类和 Circle类 。 使用抽象类 Shapes类型的指针,当它指向某个派生类的对象时,就可以通过它访问该对象的虚成员函数 。
第 8章 多态性
#include<iostream.h>
constdoublePI=3.14159;
classShapes //抽象基类 Shapes声明
{
protected:
intx,y;
public:
voidsetvalue(intxx,intyy=0){x=xx;y=yy;}
第 8章 多态性
virtualvoiddisplay()=0; //纯虚函数成员
};
classRectangle:publicShapes //派生类 Rectangle声明
{
public,//虚成员函数
voiddisplay(){cout<<"Theareaofrectangleis:"<<x*y<<endl;}
};
classCircle:publicShapes //派生类 Circle声明第 8章 多态性
{
public,//虚成员函数
voiddisplay(){cout<<"Theareaofcircleis:"<<PI*x*x<<endl;}
};
voidmain()
{
Shapes*ptr[2]; //声明抽象基类指针
Rectanglerect1;
Circlecir1;
第 8章 多态性
ptr[0]=&rect1; //指针指向 Rectangle类对象
ptr[0]->setvalue(5,8);
ptr[0]->display();
ptr[1]=&cir1; //指针指向 Circle类对象
ptr[1]->setvalue(10);
ptr[1]->display();
}
第 8章 多态性程序中类 Shapes,Rectangle和 Circle属于同一个类族,抽象类 Shapes通过纯虚函数为整个类族提供了通用的外部接口语义 。 通过公有派生而来的子类给出了纯虚函数的具体函数体实现,因此是非抽象类 。 我们可以定义非抽象类的对象,同时根据赋值兼容规则,
抽象类 Shapes类型的指针也可以指向任何一个派生类的对象,通过基类 Shapes的指针可以访问到正在指向的派生类 Rectangle和 Circle类对象的成员,这样就实现了对同一类族中的对象进行统一的多态处理 。
第 8章 多态性本例的程序运行结果为
Theareaofrectangleis:40
Theareaofcircleis:314.159
另外,程序中派生类的虚成员函数 display()并没有用关键字 virtual显式说明,因为它们与基类的纯虚函数具有相同的名称及参数和返回值,由系统自动判断确定其为虚成员函数 。