C++ 第十九讲-多态性与虚函数_第1页
C++ 第十九讲-多态性与虚函数_第2页
C++ 第十九讲-多态性与虚函数_第3页
C++ 第十九讲-多态性与虚函数_第4页
C++ 第十九讲-多态性与虚函数_第5页
已阅读5页,还剩38页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

《C++语言及编程技巧》主讲:匡纲要国防科技大学电子科学与工程学院·湖南长沙·-1第19讲

多态性与虚函数-2第20讲多态性与虚函数

C++的多态性11.1指向基类的指针和指向派生类的指针

11.2使用基类指针引用派生类对象时同名成员函数的处理

11.3静态联编和动态联编

11.4virtual成员函数---虚函数

11.5纯虚函数与抽象基类

11.6虚成员函数表

11.7虚析构函数-3C++多态性polymorphism,“manyforms”:即多种形态在自然语言中,多态性即是“一词多义”;更准确地说,多态性是指相同的动词作用到不同类型的对象上,例如:

驾驶摩托车

驾驶汽车

驾驶飞机

驾驶轮船

驾驶宇宙飞船-4

当不同对象接受到相同的消息产生不同的动作,这种性质称为多态性。通俗地说,多态性是指用一个名字定义不同的函数,这些函数执行不同但又类似的操作,即用同样的接口访问功能不同的函数,从而实现“一个接口,多种方法”。在C语言中,由于不支持多态,求绝对值的动作要求三个不同的函数名字:

abs(),labs(),fabs()分别用来求整数,长整数、浮点数的绝对值。在C++语言中,由于支持多态,求绝对值的动作可以只用一个函数名:abs()C++多态性-5多态应用于OOP的目的是允许用一个名字来指定动作的一般类(即逻辑上相似的动作)。从而带来以下的好处:提高了处理问题的抽象级别;降低了程序设计时的复杂性;(程序员只需记住一个接口,而不是好几个。)C++多态性-611.1指向基在的指针和指向派生类的指针整型指针浮点型指针双精度型指针字符型指针数组型指针函数指针结构体指针指向指针的指针__二级指针

指向基类的指针__基类指针

指向派生类的指针__派生类指针指针:-7

基类指针可以存取基类中的成员,它也可能可通过指向派生类对象来存取派生类中从基类继承来的成员,但不能存取派生类中新增添的成员。派生类指针既可以存取派生中从基类继承灰的成员,亦可存取派生类中新增添的成员。指向基类和派生类的指针是相关的。例如:

A*p; //指向类型A的对象的指针

AA_obj

; //类型A的对象

BB_obj

; //类型B的对象

p=&A_obj

; //p指向类型A的对象

p=&B_obj

; //p指向类型B的对象,

//它是A的派生类 利用p,可以通过B_obj

访问所有从A_obj

继承的元素,但不能用p访问B_obj

自身特定的元素(除非用了显式类型转换)。11.1指向基在的指针和指向派生类的指针-8bcn

bco,*pbco;dcn

dco,*pdco;pbco=&bco;pbco->seti(0);pbco->printi();cout<<pbco->geti()<<endl;pbco=&dco;pbco->seti(10);pbco->printi();cout<<pbco->geti()<<endl;classbcn{public:

inta;voidseti(intii){i=ii;}

int

geti(){return(i);} voidprinti();private:

inti;};classdcn:public

bcn{public:

inta;voidsetj(int

jj){j=jj;}

int

getj(){return(j);} voidprintj();private:

intj;};pdco=&dco;pdco->seti(20);pdco->printi();cout<<pbco->geti()<<endl;pdco->setj(30);pdco->printj();cout<<pbco->geti()<<endl;11.1指向基在的指针和指向派生类的指针第一个语句把基类指针pbco指向派生类对象dco,第二至第四个语句使用基类指针pbco,通过其已指向派生类对象dco,访问派生类dcn中从基类继承来的成员函数seti、printi以及geti。若将上述四个赋值语句改写如下:

pbco=&dco;

pbco->setj(10);

pbco->printj();

cout<<pbco->getj()<<endl;C++编译器对于其中的第二至第四个语句均示同样的出错信息。因此基类指针pbco虽已指向派生类对象dco,但也不能访问派生类中新增添的其它成员。((dcn*)pbco)->seti(10);((dcn*)pbco)->printi();cout<<((dcn*)pbco)->geti()<<endl;-911.2使用基类指针引用派生类对象时同名成员函数的处理赋值兼容规则 赋值兼容规则是指:在公有派生的情况下,一个派生类的对象可用于基类对象适用的地方。赋值兼容规则有三种情况(假定类derived由类base派生): (1)派生类的对象可以赋值给基类的对象。

derivedd; baseb; b=d;

(2)派生类的对象可以初始化基类的引用。

derivedd; base&br=d;

(3)派生类的对象的地址可以赋给指向基类的指针。

derivedd; base*pb=&d;-10classbcn{public:

inta;voidseti(intii){i=ii;}

int

geti(){return(i);} voidprint();private:

inti;};classdcn:public

bcn{public:

inta;voidsetj(int

jj){j=jj;}

int

getj(){return(j);} voidprint();private:

intj;};bcn

bco,*pbco;dcn

dco,*pdco;pbco=&bco;pbco->seti(0);pbco->print();cout<<pbco-geti()<<endl;pbco=&dco;pbco->seti(10);pbco->print();cout<<pbco-geti()<<endl;pdco=&dco;pdco->seti(20);pdco->print();cout<<pbco->geti()<<endl;pdco->setj(30);pdco->print();cout<<pbco->geti()<<endl;访问的是基类的成员函数print()访问的是基类的成员函数print()访问的是派生类的成员函数print()访问的是派生类的成员函数print()(1)、即使基类与派生类包含非重载的同名函数,通过基类指针向派生类对象以存取访该成员函数时,其结果所访问到的仍是从基类继承来的那个成员函数,而不是派生类新增添的那个同名成员函数。(2)、若使用派生类指针指向派生类对象,利用派生类指针存取同名成员函数时,则根据同名覆盖机制,其结果所访问到的是派生类新增添的那个同名函数。11.2使用基类指针引用派生类对象时同名成员函数的处理-1111.3静态联编与动态联编什么叫联编(Binding)?一个源程序需要经过编译、连接,才能成为可执行代码。上述过程中需要将一个函数调用链接上相应的函数代码,这一过程称为联编。联编的目的是要建立函数调用与函数体之间的联系,即将一个函数调用连接到一函数的入口地址。-12静态联编:在程序被编译时进行联编;(早联编)程序执行快,但灵活性较小。动态联编:在程序运行时联编。(晚联编,滞后联编)灵活性高,程序执行慢。动态联编是C++实现运行时多态性的关键因素。11.3静态联编与动态联编-1311.4virtual成员函数____虚函数

virtualfloatarea(){return-1;}virtual关键字的作用:指示C++编译器对该函数的调用进行动态联编。-1411.4virtual成员函数____虚函数#include<iostream.h>classshape{public: floatarea(){return-1;}};classcircle:publicshape{ floatradius;public: voidcircle(floatr){radius=r;} floatarea(){return3.14159*radius*radius;}};-15voidmain(){ shapeobj,*ptr; circlec(3.6);

ptr=&obj; cout<<ptr->area()<<endl;

ptr=&c; cout<<ptr->area()<<endl;}Thepointerofbaseclass“shape".求圆形的面积11.4virtual成员函数____虚函数-16输出的结果为:-1-1为什么会是这样?原因在于这里只是用了静态联编。前后两次ptr->area()调用都链接到下面的函数实现:floatarea(){return-1;}输出的结果分析11.4virtual成员函数____虚函数-17#include<iostream.h>classshape{public:

virtualfloatarea(){return-1;}};classcircle:publicshape{ floatradius;public: voidcircle(floatr){radius=r;} floatarea(){return3.14159*radius*radius;}};被关键字virtual说明的函数称为虚函数11.4virtual成员函数____虚函数-18voidmain(){ shapeobj,*ptr; circlec(3.6);

ptr=&obj; cout<<ptr->area()<<endl;

ptr=&c; cout<<ptr->area()<<endl;}Thepointerofbaseclass“shape".C++的编译器对虚函数调用采取动态联编方式输出结果:-140.71511.4virtual成员函数____虚函数-19虚函数的定义冠以关键字virtual的成员函数称为虚拟函数,简称虚函数。说明虚函数的方法如下:

virtual<类型说明符><函数名>(<参数表>)虚函数是成员函数,而且是非static的成员函数。包含虚函数的类被称为多态类。11.4virtual成员函数____虚函数-20classshape{public:

virtualfloatarea(){};};classtriangle:publicshape{ floatH,W;public: triangle(floath,floatw){H=h;W=w;} floatarea(){returnH*W*0.5;}};在派生类中,虚函数被重新定义以实现不同的操作。这种方式称为函数超越(overriding),又称为函数覆盖。虚函数的使用分析11.4virtual成员函数____虚函数-21classrectangle:publicshape{ floatH,W;public: rectangle(floath,floatw){H=h;W=w;} floatarea(){returnH*W;}};11.4virtual成员函数____虚函数-22voidmain(){shape*s;triangletri(3,4);rectanglerect(3,4);s=&tri;cout<<“Theareaoftriangle:”<<s->area()<<endl;s=▭cout<<“Theareaofrectangle:”<<s->area()<<endl;}请注意:在这里以指针s所指的对象来确定执行虚函数area()的哪个版本。“确定”是在运行时执行的,这构成了运行时的多态性11.4virtual成员函数____虚函数-23虚函数是动态联编的基础virtual关键字的作用:指示C++编译器对该函数的调用进行动态联编。尽管可以用对象名和点算符的方式调用虚函数,即向对象发送消息:tri.area()或者rect.area()这时是静态联编方式。只有当访问虚函数是通过基类指针s时才可获得运行时的多态性。11.4virtual成员函数____虚函数-24虚函数与重载函数的比较1.从形式上说,重载函数要求函数有相同的函数名称,并有不同的参数序列;2.重载函数可以是成员函数和非成员函数;而虚函数要求这三项(函数名称、返回值和参数序列)完全相同,即具有完全相同的函数原型。而虚函数只能是成员函数。3.对重载函数的调用是以所传递参数序列的差别(参数个数或类型的不同)作为调用不同函数的依据;虚拟函数是根据对象的不同去调用不同类的虚拟函数。虚函数在运行时表现出多态功能,正是C++的精髓之一;而重载函数不具备这一功能。11.4virtual成员函数____虚函数-25

如果某类中的一个成员函数被说明为虚函数,这就意味着该成员函数在派生类中可能有不同的实现。为了实现运行时的多态性,调用虚函数应该通过第一次定义该虚函数的基类对象指针只有通过指针或引用标识对象来操作虚函数时,才对虚函数采取动态联编方式。如果采用一般类型的标识对象来操作虚函数,则将采用静态联编方式调用虚函数。构造函数不能是虚拟的,但析构函数可以是虚拟的。

设计C++程序时,一般在基类中定义处理某一问题的接口和数据元素,而在派生类中定义具体的处理方法。通常都将基类中处理问题的接口设计成虚函数,然后利用基类对象指针调用虚函数,从而达到单一接口,多种功能的目的。虚函数小结11.4virtual成员函数____虚函数-2611.5纯虚函数和抽象类纯虚函数是指在基类中声明但是没有定义的虚函数,而且设置函数值等于零。

virtualtypefunc_name(parameterlist)=0;通过将虚函数声明为纯虚函数可以强制在派生类中重新定义虚函数。如果没有在派生类中重新定义,编译器将会报告错误。-27classshape{public:

virtualfloatarea()=0; //APUREVIRTUALFUNCTION};classtriangle:publicshape{ floatH,W;public: triangle(floath,floatw){H=h;W=w;} floatarea(){returnH*W*0.5;}};纯虚函数11.5纯虚函数和抽象类-28抽象类:包含有纯虚函数的类称为抽象类。不能说明抽象类的对象,但能说明指向抽象类的指针,一个抽象类只能作为基类来派生其他的类。抽象类的指针用于指向该抽象类的派生类的对象。抽象类11.5纯虚函数和抽象类-29voidmain(){shape*s;triangletri(3,4);rectanglerect(3,4);s=&tri;cout<<“Theareaoftriangle:”<<s->area()<<endl;s=▭cout<<“Theareaofrectangle:”<<s->area()<<endl;}抽象类的例子Shape是抽象类,只能定义指向该类对象的指针11.5纯虚函数和抽象类-3011.5纯虚函数和抽象类(1)、纯虚函数是在基类中定义的一种特殊虚函数,它亦使用C++关键字virtual作为前缀,但不包含实现,且初始化为0(2)、纯虚函数仅用于为将要由该基类派生的派生类定义同名成员函数占据一个位置。派生类中必须定义与基类的纯虚函数同名的成员函数,并给出具体实现。(3)、包含纯虚函数的基类称为抽象基类或抽象类。在一个基类中,只要加入至少一个纯虚函数,则该基类就成为抽象类。(4)、不能创建抽象基类的对象,似保证该抽象煁类的“纯结性”。但可以说明指向该抽象基类的指针,即抽象类指针。这对于实现运行时多态性是必要的。(5)、可以利用类的继承机制定义抽象基类的派生类。如果希望创建该派生类的对象,那么在派生类中必须覆盖基类中所有纯虚函数。(6)、纯虚函数和抽象基类为面向对象程序设计提供了一种安全机制。由于在基类中定义了纯虚函数,就需要在派生类中给出上具体实现。纯虚函数和抽象类的使用规则:-3111.6虚成员函数表略。-3211.7虚析构函数类的构造函数不能是虚函数,但类的析构函数却能。其主要目的在于解决当使用程序时的多态性来处理涉及在类层次结构中某类对象的动态内存分配及释放时,避免发生遗漏释放所分配内存空间的问题。

运算符new、delete实现对类的对象进行动态内存分配及释放。-33structstudents{

intnum; charname[15]; charsex; floatscore;};students*ps;ps=newstudents;//......deleteps;double*pd;pd=newdouble[10];//......delete[]pd;这里表达式newstudents实现为类型students的结构体变量在内存堆栈区域动态分配上片连续的内存空间,其大小可由sizeof(students)确定。而相应的赋值语句则令结构体指针ps指向该堆内存空间的首地址。需注意是,运算符new后面跟随的是一数据类型。Delete语句用以实现原先由new分配的堆内存空间。可以使用运算符new和delete实现动态分配及释放一个数组,但表现形式有所不同。为动态分配一个数组,需组合使用运算符new和[]。同样地,为动态释放原先由new[]分配的数组,需组合使用运算符delete和[]。上面由运算符new[]申请分配的动态堆内存空间大小应能存放10个double类型变量,即为80个字节。由delete[]释放的动态堆内存空间即是上述由new[]申请分配的那80个字节。11.7虚析构函数-34

运算符new、new[]或delete、delete[]亦可用于创建或撤消类的对象。使用new创建一个对象时,不仅在内存堆区为该对象动态分配指定大小的堆内存空间,还同时调用该类的构造函数,为该对象做初始化工作。类似地,使用delete以动态地撤消原先由new所创建的对象时,它先调用该类的析构函数,进行必要的清理收尾工作,然后再释放该对象所占据的动态内存。11.7虚析构函数-35//programc11_06.cpp#include<iostream>usingnamespacestd;classone{public:one(){

cout<<"classone'sconstructor"<<endl;pd=newdouble[10];

cout<<"dynamicmemoryallocation:"<<sizeof(double)*10<<"bytes"<<endl;}~one()

{

cout<<"classone'sdestructor"<<endl;delete[]pd;

cout<<"dynamicmemoryfree:"<<sizeof(double)*10<<"bytes"<<endl;}private:double*pd;};11.7虚析构函数-36classtwo:publicone{public:

two():one(){

cout<<"classtwo'sconstructor"<<endl;

qd=newdouble[100];

cout<<"dynamicmemoryallocation:"<<sizeof(double)*100<<"bytes"<<endl;}~two(){

cout<<"classtwo'sdestructor"<<endl;delete[]qd;

cout<<"dynamicmemoryfree:"<<sizeof(double)*100<<"bytes"<<endl;}private:double*qd;};11.7虚析构函数-37classthree:publictwo{public:

three():two(){

cout<<"classthree'sconstructor"<<endl;rd=newdouble[1000];

cout<<"dynamicmemoryallocation:"<<sizeof(double)*1000<<"bytes"<<endl;}~three(){

cout<<"classthree'sdestructor"<<endl;delete[]rd;

cout<<"dynamicmemoryfree:"<<sizeof(double)*1000<<"bytes"<<endl;}private:double*rd;};11.7虚析构函数-38voidmain(){ one*pco1; pco1=newtwo; deletepco1;

cout<<endl; pco1=newthree; deletepco1;}11.7虚析构函数不论是将基类指针pco1指向派生类对象two还是指向派生类对象three,所调用的析构函数均是基类的析构函数~one()。因此上述程序C++编译器实施的是静态联编,它根据基类指针pco1的数据类型即one*来决定调用哪一个析构函数。在这种情况下就发生可能有一大批动态分配内存空间被遗漏而未释放。要解决以上发生的内存遗漏问题,唯一要做的事,就是把类层次结构中各个类的析构函数均定义为虚函数。-39classone{public:one(){

cout<<"classone'sconstructor"<<endl;pd=newdouble[10];

cout<<"dynamicmemoryallocation:"<<sizeof(double)*10<<"bytes"<<endl;}

virtual~one(){

cout<<"classone's

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论