C++课件第10章多态Word文件下载.docx

上传人:b****4 文档编号:17868018 上传时间:2022-12-11 格式:DOCX 页数:9 大小:21.25KB
下载 相关 举报
C++课件第10章多态Word文件下载.docx_第1页
第1页 / 共9页
C++课件第10章多态Word文件下载.docx_第2页
第2页 / 共9页
C++课件第10章多态Word文件下载.docx_第3页
第3页 / 共9页
C++课件第10章多态Word文件下载.docx_第4页
第4页 / 共9页
C++课件第10章多态Word文件下载.docx_第5页
第5页 / 共9页
点击查看更多>>
下载资源
资源描述

C++课件第10章多态Word文件下载.docx

《C++课件第10章多态Word文件下载.docx》由会员分享,可在线阅读,更多相关《C++课件第10章多态Word文件下载.docx(9页珍藏版)》请在冰豆网上搜索。

C++课件第10章多态Word文件下载.docx

下一章再讲。

⏹函数覆盖:

这是本章所要讨论的。

一、静态联编和动态联编

联编是指一个计算机程序自身彼此关联的过程。

按照联编所进行的阶段不同,可分为两种不同的联编方法:

静态联编和动态联编。

1静态联编

  静态联编是指联编工作出现在编译连接阶段,这种联编又称早期联编,因为这种联编过程是在程序开始运行之前完成的,在编译时就解决了程序中的操作调用与执行该操作代码间的关系。

下面举一个静态联编的例子,该例子是图形面积的计算。

例1:

classPoint{//该类计算点的面积

private:

doublex,y;

//点的坐标

public:

Point(doublei,doublej){x=i;

y=j;

}//构造函数

doublearea()const{return0.0;

}//计算面积

};

classRectangle:

publicpoint{//计算矩型面积

doublew,h;

//矩型长、宽

//构造函数,包含对父类成员变量的初始化

Rectangle(doublei,doublej,doublek,doublel);

//覆盖了父类同名函数area()

//area()const表示函数内不可更改成员变量的值,

//保证在该函数内使用的成员变量只是传值,并不发生值的变化

doublearea()const{returnw*h;

}

Rectangle:

:

Rectangle(doublei,doublej,doublek,doublel):

point(i,j)

{w=k;

h=l;

voidfun(Point&

s)//全局函数,传递对象引用

{cout<

s.area();

}

voidmain(){

Rectanglerec(3.0,5.2,15.0,25.0);

fun(rec);

}

该程序的运行结果为:

0。

算出了点的面积而不是矩形面积,为什么?

 虽然实例化了一个矩形对象Rectangle:

但是,在调用fun(rec)时,由于Rectangle类继承了Point类,因此Rctangle对象rec实例化时肯定要实例化一个Point,而函数voidfun(Point&

s)的形参类型正好是Point对象引用,因此编译器在编译时把函数实参绑定到Point对象的引用上。

这是静态联编的结果,导致程序输出了所不期望的结果。

虽然上面的例子将voidfun(Point&

s)改为voidfun(Rectangle&

s)可轻易解决问题,但这不符合多态“一个概念,多种实现”的思想,我们希望的是;

维持Point这“一个概念”,让voidfun(Point&

s)作为对外服务的接口,持有一个指向Point的对象&

s,而在具体的矩型、多边型、圆型对象中,动态的按需调用所需的对象。

这需要动态联编技术。

2动态联编  

动态联编实际可以在程序运行时动态识别所需调用的对象。

c++规定动态联编是在虚函数的支持下实现的。

二、虚函数

1虚函数基本特点

如果某类中的一个成员函数被说明为虚函数,这就意味着该成员函数在派生类中可能有不同的实现。

当使用一个指针或引用所标识对象操作该成员函数时,对该成员函数调用采取动态联编方式。

 

声明一个成员函数为虚函数,只需在函数声明前加virtual关键字即可。

C++对virtual函数进行动态联编。

下面将例1的area()声明为虚函数:

例2(为简化,删掉了构造函数):

classPoint{//该类计算点的面积

virtualdoublearea()const{return0.0;

}//虚函数

//矩型长、宽

//子类也可以不写virtual

virtualdoublearea()const{returnw*h;

}};

输出结果是375。

因为area()声明为虚函数,C++对其动态联编,运行时确定,将fun(rec)中的&

s形参绑定到子类对象rec。

形象的说,动态联编是“自下向上”绑定对象的,先看看有没有本类对象,有则绑定本类对象,没有则从最低一级逐级向上绑定,如例2所示。

而静态联编是“自上向下”先看看本类有没有合适对象,有则绑定本类对象,没有则从最高一级开始向下查找绑定对象的,如例1所示。

2运行时多态的好处

可以只曝露较抽象的基类,而隐藏具体类的实现,用户调用抽象的服务得到的是具体的服务。

运行时多态也有利于实现分布式智能化服务。

这些好处是与具体语言无关的。

对例2作伪代码扩展,可以看出运行时多态的好处。

例3(伪代码):

classPOINT{virtualarea()}//抽象服务类

//以下是具体服务类,继承了POINT

classSHAPE1:

POINT{virtualarea()}

classSHAPE2:

POINT{virtualarea()}

classSHAPE3:

fun(Point&

s)//一个抽象服务接口

{s.area()}

SHAPE1S1;

fun(S1);

//具体服务,系统动态确定

SHAPE1S2;

fun(S2);

SHAPE1S3;

fun(S3);

用户只需知道fun()的位置,知道fun()提供计算面积的服务,需要计算具体图形面积时,创建一个该图形对象即可,不必关心这个具体图形面积是哪个对象计算的。

这是一种非常优雅的体系结构。

3进一步理解虚函数

C++动态联编必需的条件是:

⏹要建立子类型关系。

⏹要有虚函数的说明,只有虚函数才可以动态联编。

⏹要虚函数发挥作用,必须用基类的指针(或引用)指向派生类的对象(比如例2中的fun(Point&

s)),

⏹只有地址才能体现多态性,要用指针(或引用)调用虚函数。

如果在一个函数内调用虚函数,如果默认的this指针有效,是符合本条要求的。

例4符合上述要求的动态联编:

#include 

<

iostream.h>

classA 

 

public:

virtualvoidact1();

//有虚函数 

voidact2(){

act1();

//类成员调用虚函数,有默认的this指针

this->

//类函数调用虚函数,显式的this 

A:

}// 

使用了类限定符,this无效,静态联编

voidA:

act1(){cout<

"

act1()called"

;

classB:

publicA{//建立子类型关系 

virtualvoidact1();

//有虚函数

};

void 

B:

act1() 

{cout<

act1()called“;

voidmain() 

Bb;

b.act2();

运行结果是:

act1()calledB:

act1()calledA:

act1()called

3构造函数调用虚函数

在“第9章继承”曾讲过,派生类构造函数的调用顺序如下:

●先调基类的构造函数产生基类对象

●再调用派生类构造函数

当构造函数内调用虚函数时,仍沿用上述规则,但对虚函数的调用采用的是静态联编而不是动态联编, 

为什么会这样,先看例5:

例5:

构造函数中调用虚函数。

classA{

A(){} 

//无参构造函数

virtualvoidf(){cout<

f()called."

classB:

publicA{ 

B(){f();

}//构造函数中调用虚函数 

voidg(){f();

classC:

publicB 

C(){} 

virtualvoidf(){cout<

C:

voidmain() 

Cc;

c.g();

输出结果为:

f()called.C:

f()called.

下面分析该程序的运行过程:

(1)当执行Cc;

时,调用构造函数初始化对象c,按构造函数调用规则:

●先调A的构造函数,无输出。

●再调B的构造函数,B()要调虚函数f(),由于B没有实现f(),按虚函数动态绑定“自下而上”的规律,应该先绑定C:

f(),但此时C的构造函数还没有被调用,无法绑定C:

f()。

C++只好规定,构造函数内调用虚函数时用静态绑定。

于是按静态绑定的规律:

●向上找A类,有,输出A:

f()called。

(2)当执行c.g();

c对象已实例化了(所有构造函数都调用完成),回到正常的虚函数动态绑定“自下而上”的规律,g()函数内绑定了C:

f(),输出C:

(3)通过上述讨论可知:

C++之所以规定构造函数内调用虚函数是静态联编,原因是无法找到动态联编所需的对象(比如本例中的对象c)。

4语法约定

对于虚函数的使用,C++做了一些规定,把重要的几条罗列如下:

⏹派生类虚函数的返回类型、参数个数要与基类一样。

⏹派生类虚函数参数类型要与基类一样,否则C++按基类虚函数形参类型进行强制转换。

⏹派生类虚函数声明可以不写virtual,只在基类中写就可以了。

5虚函数和虚表

简要讨论一下虚函数在c++中的实现机制。

虚函数在c++中的实现机制用的是虚表和虚指针。

每个类用了一个虚表,每个类的对象用了一个虚指针。

对于以下代码:

classA{

virtualvoidf();

virtualvoidg();

inta};

classB:

publicA{

voidg();

intb;

A有virtualvoidf()和virtualvoidg(),编译器为A类准备了一个虚表vtableA,:

f()的地址

g()的地址

因为B继承了A,所以编译器也为B准备了一个虚表vtableB,内容如下:

注意:

因为B:

g是重写了的,所以B的虚表的g放的是B:

g的入口地址,但是f是从A继承的,所以f的地址是A:

f的入口地址。

然后当Bb实例化对象的时候,编译器开始给对象分配空间,除了A的inta,B的成员intb;

以外,还分配了一个虚指针vptr,指向B的虚表vtableB,对象b的布局如下:

vptr:

指向B的虚表vtableB

inta:

继承A的成员

intb:

B成员

当有如下符合动态联编条件的语句:

A*pa=&

b;

pa能访问到b对象的前两项,但访问不到第三项intb。

这行代码反映了多态思想的关键:

将已创建的子类对象的引用或指针指向父类对象---可以是显式的也可以是隐式,这样可以使父类对象为子对象提供外界访问的接口(JAVA也是这样要求的,需要将一个子类对象的引用隐式或显式的传给父类对象),由父类对象对虚函数的访问进行动态绑定。

当pa->

g()开始绑定对象时,编译器知道的是,g()是一个声明为virtual的成员函数,而且其入口地址放在表格(无论是vtalbeA表还是vtalbeB表)的第2项,那么编译器编译这条语句的时候就如是转换:

call*(pa->

vptr)[2]),(这一行可看作伪代码)。

这一项放的是B:

g()的入口地址,则就实现了多态。

(注意b的vptr指向的是B的虚表vtableB)。

C++标准只要求用虚标机制实现多态,至于虚指针vptr到底放在一个对象布局的哪里,标准没有要求,每个编译器自己决定。

如果继承体系的基类的virtual成员不多,而且在派生类要重写的部分占了其中的大多数时候,用C++的虚函数机制是比较好的;

但是如果继承体系的基类的virtual成员很多,或者是继承体系比较庞大的时候,而且派生类中需要重写的部分比较少,那就用另一种多态实现机制:

“名称查找表”效率会高一些,很多的GUI库都是这样的。

三、纯虚函数和抽象类

JAVA语言中有抽象类和接口的语法。

JAVA引入接口的目的主要是将抽象类与子类间的isakind关系与接口与实现类的关系区分开。

C++有抽象类而无接口的语法。

但C++的抽象类也可当作接口使用。

1纯虚函数

纯虚函数是一种特殊的虚函数,它的一般格式如下:

class 

类名>

virtual 

返回类型函数名(参数表)=0;

… 

在许多情况下,在基类中不能对虚函数给出有意义有实现,而把它说明为纯虚函数,它的实现留给该基类的派生类去做。

这就是纯虚函数的作用。

2抽象类 

带有纯虚函数的类称为抽象类。

抽象类是一种特殊的类,它是为了抽象和设计的目的而建立的,它处于继承层次结构的较上层。

抽象类是不能定义对象的,在实际中为了强调一个类是抽象类,可将该类的构造函数说明为保护的访问控制权限。

抽象类的主要作用是将有关的组织在一个继承层次结构中,由它来为它们提供一个公共的根,相关的子类是从这个根派生出来的。

抽象类刻画了一组子类的操作接口的通用语义,这些语义也传给子类。

一般而言,抽象类只描述这组子类共同的操作接口,而完整的实现留给子类。

抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。

如果派生类没有重新定义纯虚函数,而派生类只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。

如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体类了。

本章小结:

动态联编下的多态性(主要体现在函数覆盖)是不太容易理解的,可以分成几个层次理解它:

1设计概计和思想,本章的例3作了较好的说明。

这是与语言无关的,也是最重要的。

2知道动态联编是“自下向上”而静态联编是“自上向下”绑定对象,对于阅读和编写程序很有帮助。

这虽是实现层面的东西,但几乎也与语言无关,因为面向对象语言都按此顺序绑定对象。

3具体到C++,通过理解虚表,理解动态联编是怎样实现的,为什么需要指针或引用调用虚函数。

本章课后习题:

理解例1-例5的代码

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 工程科技 > 能源化工

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1