面向对象程序设计(C++语言)三ppt.ppt_第1页
面向对象程序设计(C++语言)三ppt.ppt_第2页
面向对象程序设计(C++语言)三ppt.ppt_第3页
面向对象程序设计(C++语言)三ppt.ppt_第4页
面向对象程序设计(C++语言)三ppt.ppt_第5页
已阅读5页,还剩194页未读 继续免费阅读

下载本文档

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

文档简介

李爱华程磊编著清华大学出版社同名教材配套电子教案,2010年2月,面向对象程序设计(C+语言)3,2,第二部分面向对象的程序设计,第3章类和对象(一)第4章类和对象(二)第5章继承和派生第6章虚函数与多态性第7章运算符重载第8章模板第9章标准模板库STL第10章C+语言的输入和输出,3,第5章继承和派生,本章重点:继承与派生的概念;派生类的构造函数和析构函数的执行顺序与规则;多继承的声明与实现;基类成员访问原则;赋值兼容性;虚基类的概念;,4,5.1继承与派生的概念,继承是面向对象程序设计中重要的特性。继承主要是指在己有类的或称为基类的基础上创建新类的过程,这个新类就是派生类。派生类自动的包含了基类的成员,包括所有的数据和操作,而且它还可以增加自身新的成员,5,在C+中,一个派生类可以从一个基类派生,也可以从多个基类派生,从一个基类派生的称为单继承,如图5.1。图5.2中的树型结构图可以体现学生体系的概念。,图5.1单继承,图5.2类的层次树型结构图,6,一个派生类从两个或多个基类派生则称为多继承,如图5.3所示,它使一个类可以融合了多个类的特征,例如在现实生活中的在职研究生的概念,他是在职人员,又是研究生,如图5.4所示,从图中还可以看到一个有趣的现象,在职人员类本身是单继承的基类,教师和职员都是它的具体子类,而其又是在职研究生的多重基类,提供在职人员的基本特征。,图5.3多继承,图5.4单继承与多继承,7,继承机制除了支持软件复用外,还具备以下三个作用:对事物进行分类。支持软件的增量开发。对概念进行组合。,8,前面的知识中,我们学习了如何定义类和如何实现类的抽象与封装,通常在不同的类中,数据成员和函数成员都是不同的,但对于某些特定的问题,有时候两个类的基本或大部分内容是相同的,在图5.2中给出了学生体系的概念,我们利用现有知识可以首先声明了一类来描述学生这一基本概念,如下:classStudentprivate:intnumber;charname20;public:Student()number=0;name0=0;VoidSetValue(intn,char*s1)number=n;Strcpy(name,s1);voidPrint()cout”Number:”numberendl;cout”Name:”nameendl;;,9,如果现在需要一个新类UGStudent来描述大学生概念,除上述的基本成员外,还需要用到年龄、年级等信息,我们可以如下定义此类:classUGStudentprivate:intnumber;charname20;intage;intgrade;public:voidPrint()cout”Number:”numberendl;cout”Name:”nameendl;cout”Age:”ageendl;cout”Grade:”gradeendl;;,10,5.2派生类的声明,在C+中类的继承关系可以用如下语法表示:class派生类名:继承方式基类名派生类成员声明;需要注意的是,基类的构造函数和析构函数不能被派生类继承,派生类若要初始化基类的数据成员必须在构造函数中初始化。,11,【例5.1】用继承重新定义UGStudent类。,/*05_01.cpp*/classUGStudent:publicStudentprivate:intage;intgrade;public:UGStudent()SetValue(0,”);age=0;grade=0;UGStudent(intn,char*s1,inta,intg)SetValue(n,s1);age=a;grade=g;voidPrintExtra()cout”Age:”ageendl;cout”Grade:”gradeendl;,12,用主函数进行测试:intmain()UGStudentst1(100,”wang”,18,1);st1.Print();/调用基类的函数st1.PrintExtra();/调用派生类新定义的函数return0;,13,Number:100Name:wangAge:18Grade:1,程序的运行结果为:,14,图5.5基类与派生类中的成员,15,5.3派生类的访问属性,类的成员可以分为public(公有)、protected(保护)和private(私有)三种访问权限。类的非静态成员函数可以访问类中的所有成员,但是通过类的“对象.成员”方式(在类的作用域之外),则只能访问该类的公有成员。类的继承方式有公有继承(public)、保护继承(protected)和私有继承(private)三种。不同的继承方式,导致原有基类成员在派生类中的访问属性也有所不同。,16,表5.1不同继承方式下的访问控制权限,17,对于静态成员来说,与普通成员函数组合,将产生以下两种情况:(1)派生类中静态函数对基类中静态成员的访问。(2)派生类的普通成员函数要访问基类中的静态成员。静态成员的访问控制变化完全遵循表5.1的规则,这两种情况和派生类中普通成员函数访问基类中普通成员没有区别。,18,【例5.2】公有继承时的访问控制权限。,/*05_02.cpp*/#includeusingnamespacestd;classBaseprivate:inta;voidfun1()coutaendl;protected:intb;voidfun2()coutcendl;public:intc;voidfun3()coutbendl;voidseta(inti)/修改私有成员a的值a=i;intgeta()/返回私有成员a的值returna;Base(inti,intj,intk)/基类的构造函数a=i;b=j;c=k;,19,classSub:publicBaseprivate:intd;public:Sub(inti,intj,intk,intm):Base(i,j,k)/派生类构造函数d=m;voidtest()/coutaendl;/错误,无法访问基类的私有成员coutbendl;/正确,可以访问基类的保护成员coutcendl;/正确,可以访问基类的公有成员/fun1();/错误,无法访问基类的私有成员fun2();/正确,可以访问基类的保护成员fun3();/正确,可以访问基类的公有成员seta(10);/正确,间接访问基类成员acoutdendl;/正确,可以访问派生类的私有成员;,20,intmain()Baseb1(5,6,7);/coutb1.a;/错误,无法访问对象的私有成员/coutb1.b;/错误,无法访问对象的保护成员coutb1.cendl;/正确,可以访问对象的公有成员coutb1.geta()endl;/正确,间接访问对象的私有成员aSubs1(11,15,19,22);s1.test();/正确,可以访问对象的公有成员s1.c=200;/正确,可以访问对象的公有成员s1.fun3();/正确,可以访问对象的公有成员return0;,21,5.4派生类的构造函数和析构函数,在继承机制中,基类的构造函数和析构函数是不能继承的。派生类的构造函数负责对来自基类数据成员和新增加的数据成员进行初始化。所以在执行派生类的构造函数时,需要调用基类的构造函数。,22,5.4.1派生类构造函数和析构函数的执行顺序,通过继承,派生类得到了基类的成员,因此派生类对象中既包括自身类的数据成员还包括通过继承从基类中得到的数据成员。在派生类中还可用其他类来定义对象作为成员,又涉及到派生类中对象成员的构造问题,则当用派生类定义对象后,派生类对象、对象成员、基类对象的构造函数的调用顺序如下:基类的构造函数。对象成员的构造函数(如果有的话),有多个时按声明的顺序。派生类的构造函数。,23,【例5.3】派生类构造实例。,/*05_03.cpp*/#includeusingnamespacestd;classBpublic:B()cout”ConstructB”endl;classCpublic:C()cout”ConstructC”endl;classD:publicBprivate:Cc1;public:D()cout”ConstructD”endl;intmain()Dd1;return0;,24,constructBConstructCConstructD,程序的运行结果为:,25,析构函数与构造函数执行的顺相反,将按如下顺序执行:(1)派生类的构造函数;(2)对象成员的构造函数(如果有的话),有多个时与声明的顺序相反;(3)基类对象的析构函数。,26,对例5.3中的程序进行改造,为每一个类添加析构函数,然后再进行测试。,classBpublic:B()cout”ConstructB”endl;B()cout”DeconstructB”endl;classCpublic:C()cout”ConstructC”endl;C()cout”DeconstructC”endl;classD:publicBprivate:Cc1;public:D()cout”ConstructD”endl;D()cout”DeconstructD”endl;intmain()Dd1;return0;,27,ConstructBConstructCConstructDDestructDDestructCDestructB,程序的运行结果为:,28,5.4.2派生类构造函数和析构函数的构造规则,在C+中,类的机制非常清楚、严格地划分了各自的权限和责任。是哪个类的操作,必须由那个类调用;是谁的对象,就必须由该类的构造函数来完成对其构造的工作。因此,对派生类中基类成员的构造,必须由基类构造函数完成,而不能由派生类的构造函数越权去构造。派生类构造函数主要负责调用基类构造函数并提供基类构造函数所需的参数。,29,下面分两种情况讨论派生类对象的构造:,1如基类中定义了默认构造函数,且该默认构造函数能够完成派生类对象中基类成员的构造,则派生类构造函数无需显式调用基类构造函数,直接调用基类的默认构造函数即可,这是一种较为简单的情况,例5.3中的继承就属于此类情况,下面的例子也可以说明这一点。,30,【例5.4】分析下面程序的输出结果。,/*05_04.cpp*/#includeusingnamespacestd;classBasepublic:Base()a=0;Base(inti)a=i;protected:inta;classDerived:publicBasepublic:Derived()b=0;Derived(inti)b=i;voidPrint()couta=a,b=bendl;private:intb;,31,intmain()Derivedd1;Derivedd2(12);d1.Print();d2.Print();return0;,32,a=0,b=0a=0,b=12,程序的运行结果为:,33,2若基类中定义了有参数的构造函数,或者所定义的默认构造函数不能完成基类成员的构造,则必须通过派生类构造函数显式向调用基类的构造函数,向带参数的构造函数传递参数,这需要用到“成员初始化列表”的语法。另外,对于派生类中普通数据成员的初始化,以及对象成员的构造也可以放在成员初始化列表中完成。此时这些以逗号隔开的基类构造函数调用数据成员初始化和构造的放置顺序可以是任意的。因此派生类的构造函数定义一般格式如下:派生类名(参数列表):基类构造函数(参数列表1),子对象成员1(参数列表)派生类构造函数体,34,【例5.5】派生类构造函数举例。,/*05_05.cpp*/#include#includeusingnamespacestd;classDate/日期类private:intyear,mon,day;/年、月、日成员变量public:Date(inty=2009,intm=6,intd=10)/构造函数year=y;mon=m;day=d;voidPrint()/输出数据,格式为:年-月-日coutyear-mon-dayendl;,35,classStudent/定义学生基类protected:intnumber;/数据成员charname20;charsex;public:Student()/重定义的默认构造函数number=0;strcpy(name,Noname);/默认名字sex=M;/默认性别,男性(male)dStudent(intn,char*s,charx)/带参数的构造函数number=n;strcpy(name,s);sex=x;,36,/大学生派生类classUGStudent:publicStudentpublic:UGStudent(intn,char*s,charx,inta,inty,intm,intd):Student(n,s,x),birth(y,m,d)age=a;UGStudent()/此处省略了Student()调用age=0;voidPrint()/输出信息coutnumber:numberendl;coutname:nameendl;coutsex:sexendl;coutage:ageendl;coutbirthday:;birth.Print();private:intage;Datebirth;/对象成员;,37,/主函数intmain()UGStudentst1;/用派生的默认构造函数定义对象UGStudentst2(1001,Zhang,F,20,2009,6,11);/带参数构造st1.Print();st2.Print();return0;,38,number:0name:Nonamesex:Mage:0birthday:2009-6-10number:1001name:Zhangsex:Fage:20birthday:2009-6-11,程序的运行结果为:,39,5.5多继承,5.5.1多继承的声明多继承下派生类的声明格式如下:class派生类名:继承方式1基类名,继承方式2基类名2派生类类体;其中,继承方式1、继承方式2是三种继承方式:public、private和protected之一。,40,如下为最基本的定义形式:classB1;classB2;classD:publicB1,publicB2;,41,图5.6多继承类D中的成员,42,5.5.2多继承的构造函数与析构函数,在多继承的情况下,派生类的构造函数格式如下:派生类名(参数列表):基类名1(参数表1),基类名2(参数表2),子对象名(参数表n)派生类构造函数体;,43,多继承下派生类的构造函数与单继承下派生类构造函数相似,它必须同时负责该派生类所有基类构造函数的调用同时,派生类的参数个数必须包含完成所有基类初始化所需的参数个数。派生类构造函数执行顺序是先执行所有基类的构造函数,再执行派生类本身构造函数。处于同一层次的各基类构造函数的执行顺序取决于声明派生类时所指定的各基类顺序,与派生类构造函数中所定义的成员初始化列表的各项顺序无关。另外,析构函数的调用顺序则与构造函数完全相反。,44,【例5.6】多继承举例。,/*05_06.cpp*/#include#includeusingnamespacestd;/定义研究生基类classGStudentprotected:intnumber;/学号charname20;/名字charsex;/性别,男性:M,女性:Fpublic:GStudent(intn,char*s,charx)/带参数的构造函数number=n;strcpy(name,s);sex=x;coutConstructGStudent.endl;GStudent()/析构函数coutDestructGStudent.endl;,45,classEmployee/职员类protected:charename20;/职员名字charjobname20;/工作名public:Employee(char*sn,char*sj)/构造函数strcpy(ename,sn);strcpy(jobname,sj);coutConstructEmployee.endl;Employee()/析构函数coutDestructEmployee.endl;,46,/在职研究生类,从两个基类派生classGStudentHasJob:publicGStudent,publicEmployeepublic:GStudentHasJob(intn,char*s,charx,char*sj):GStudent(n,s,x),Employee(s,sj)coutConstructGStudentHasJob.endl;GStudentHasJob()coutDestructGStudentHasJob.endl;voidPrint()/输出信息coutnumber:numberendl;coutname:nameendl;coutsex:sexendl;coutjob:jobnameendl;,47,/主函数intmain()/定义一个在职研究生对象,并对其初始化GStudentHasJobst(1001,zhang,F,teacher);st.Print();return0;,48,ConstructGStudent.ConstructEmployee.ConstructGStudentHasJob.number:1001name:zhangsex:Fjob:teacherDestructGStudentHasJob.DestructEmployee.DestructGStudent.,程序的运行结果为:,49,5.6基类成员访问和赋值兼容性,5.6.1基类成员名的限定访问和名字覆盖若多个基类中定义有同名成员,则派生类对这些同名成员的访问可能存在冲突。为避免可能出现的成员访间冲突,需要用成员名限定的方法显式地指定要访问的成员。,50,【例5.7】成员访问冲突。,/*05_07.cpp*/#includeusingnamespacestd;classMP3Player/mp3播放器类public:voidPlay()/播放音乐操作coutPlaymp3music.endl;classVideoPlayer/视频播放器类public:voidPlay()/播放视频操作coutPlayvideo.endl;/新型的mp4播放器类classMP4Player:publicMP3Player,publicVideoPlayerpublic:/*/;,51,intmain()MP4Playermp4;/mp4.Play();/去掉注释,本行将产生Play()函数访问不明确的错误mp4.MP3Player:Play();mp4.VideoPlayer:Play();return0;,52,Playmp3music.Playvideo.,程序的运行结果为:,53,对于多继承,如果在不同的基类中定义了同名的成员,在派生类中要区分成员的来源,就必须使用成员名限定方法,即在成员名前加上各自基类的访问域限制即可。,54,【例5.8】多继承中成员限定法。,/*05_08.cpp*/#includeusingnamespacestd;classB1/基类B1public:intm;/成员变量mB1()/构造函数m=0;classB2/基类B2public:intm;/成员变量mB2()/构造函数m=100;,55,classD:publicB1,publicB2/派生类Dpublic:voidTest()/coutmendl;/此语句将引起二义性错误coutB1:m=B1:mendl;/输出基类B1中的成员m的值coutB2:m=B2:mendl;/输出基类B2中的成员m的值;intmain()/主函数Dd1;/派生类对象d1.Test();return0;,56,B1:m=0B2:m=100,程序的运行结果为:,57,5.6.2名字覆盖,当派生类中定义有与基类中同名的成员时,则从基类中继承得到的成员被派生类的同名成员覆盖,派生类对基类成员的直接访问将被派生类中该成员取代,为访问基类成员,必须采用成员名限定方法。,58,【例5.9】成员访问冲突。,/*05_09.cpp*/#includeusingnamespacestd;classCircle/定义圆类protected:floatradius;/半径public:Circle(floatr)/构造函数radius=r;floatArea()/求圆面积return3.14f*radius*radius;,59,classCylinder:publicCircle/圆柱体派生类private:floatheight;/高度public:Cylinder(floatr,floath):Circle(r)/构造函数height=h;floatArea()/求圆柱体面积,覆盖了基类的Area()函数floatbotarea=0,sidearea=0;botarea=Circle:Area()*2;/底面积*2sidearea=2*3.14f*radius*height;/侧面积returnbotarea+sidearea;floatVolume()/圆柱体积returnCircle:Area()*height;/基类求面积乘高度;,60,intmain()Cylindercr1(10,5);/定义圆柱体对象coutBottomArea=cr1.Circle:Area()endl;/访问基类成员coutArea=cr1.Area()endl;/访问派生类成员coutVolume=cr1.Volume()endl;return0;,61,BottomArea=314Area=942Volume=1570,程序的运行结果为:,62,从上述程序可以看出,用派生类对象访问与基类中同名的成员时,会调用本类中的成员,而不会访问到基类成员。为访问到基类的同名成员,需要以成员名限定的方法来指明,如在Cylinder类中的求面积和体积函数就用到了基类的Circle:Area()函数。另外,要注意的一点是,在派生类如果定义了和基类中同名的函数,则基类中所有的同名的重载函数都将被覆盖,即在派生类中或通过派生类对象都无法直接访问基类的任何一个同名函数,如图5.7中定义了一个基类和派生类,,63,图5.7派生类对基类的名字覆盖,64,5.6.3赋值兼容规则,在派生类对象和基类对象之间赋值时需要注意赋值的方向,即这些赋值操作需要满足赋值兼容规则。赋值兼容规则包括:(1)基类对象可以赋值给基类对象,也可以把派生类对象赋值给基类对象。(2)基类指针可以指向基类对象,也可以指向派生类对象。(3)基类引用可以指向基类对象,也可以指向派生类对象。,65,例如,有基类Base和其派生类Derived,可以定义相应的对象、指针:Baseb1;Base*pb;Derivedd1;根据赋值兼容规则,在基类Base对象可以出现的任何地方都可以用派生类Derived对象来替代。(1)派生类对象可以赋值给基类对象,即派生类对象中来自基类成员,逐个赋值给基类对象的成员:b1=d1;(2)派生类的对象也可以初始化基类对象的引用:Base,66,【例5.10】赋值兼容实例。,/*05_10.cpp*/#includeusingnamespacestd;classBase/基类Baseprotected:intmember;public:Base()member=0;voidShow()/共有成员函数coutBase:Show():memberendl;classDerived1:publicBase/第1个派生类Derived1public:Derived1(inta)member=a;voidShow()/重写共有成员函数ShowcoutDerived1:Show():memberendl;,67,classDerived2:publicDerived1/第2个派生类Derived2public:Derived2(inta):Derived1(a)voidShow()/重写共有成员函数ShowcoutShow();voidTest(Base,68,Base:Show():0Base:Show():5Base:Show():5Base:Show():10,程序的运行结果为:,69,5.6虚基类,5.6.1提出问题在多继承关系中,如果某个派生类D的多个基类(如类Bl和B2)派生自另一个公共基类B0,则在派生类对象中,会通过不同的继承路径多次得到基类B0的成员,即同时存在多份基类B0的成员。通过派生类D的对象访问这些成员时,会出现对这些成员的访问冲突。为解决冲突问题可以使用成员名限定的方法来唯一标识某个成员所属的基类,但是这不能从根本上解决问题:派生类对象中存在基类成员的多个副本,如图5.8所示。,70,图5.8多继承关系及派生类的成员构成UML图,71,5.6.2虚基类的概念,为使得公共基类B0在派生类D0中只产生一份基类成员,则需要将这个共同基类B0设置为虚基类,让基类Bl和B2从基类B0虚拟继承,这时从不同的路径继承过来的同名数据成员在派生类中就只有一个副本。同一个函数名也只有一个映射。这样就解决了同名成员的唯一标识问题。使用虚基类,可以使公共基类的成员在其间接派生类中只保留一份。使用虚基类后,4个类之间的关系如图5.9a所示,这时派生类中的成员如图5.9b所示,72,图5.9虚基类多继承关系及派生类的成员构成UML图,73,定义虚基类的格式如下:class派生类名:virtual继承方式基类名称;,74,在图5.9中的几个类可以写成如下形式:classB0public:intnv0;classB1:virtualpublicB0public:intnv1;classB2:virtualpublicB0public:intnv2;classD0:publicB1,publicB2public:intnv3;,75,5.6.3虚基类的初始化,关于虚基类的初始化,有如下两条规则:(1)所有从虚基类直接或者间接派生的类必须在该类构造函数的成员初始化列表列出对虚基类构造函数的调用,但是只有实际构造对象的类的构造函数才会引发对虚基类构造函数的调用,而其他基类在成员初始化列表中对虚基类构造函数的调用都会被忽略,从而保证了派生类对象中虚基类成员只会被初始化一次。(2)若某类构造函数的成员初始化列表中同时列出对虚基类构造函数和非虚基类构造函数的调用,则会优先执行虚基类的构造函数,76,【例5.11】设置虚基类以解决二义性,/*05-11.cpp*/#includeusingnamespacestd;classBase/虚基类Basepublic:Base(inta)/构造函数val=a;voidPrint()/输出成员val值得函数coutvalendl;protected:intval;/成员变量val;classDerived1:virtualpublicBase/第一个派生类Derived1public:Derived1(intx,inty):Base(x),dv1(y)/调用了基类的构造函数protected:intdv1;,77,classDerived2:virtualpublicBasepublic:Derived2(intx,inty):Base(x),dv2(y)/调用了基类的构造函数protected:intdv2;/最终的多重继承类DerivedFinalclassDerivedFinal:publicDerived1,publicDerived2public:DerivedFinal(intx,inty,intz):Derived1(x,y),Derived2(y,z),Base(z);intmain()DerivedFinaldf(7,18,22);/定义对象df.Print();/输出虚基类Base的成员val的值return0;,78,22,程序的运行结果为:,79,5.7本章小结,1类的继承方式类的继承方式有public(公有继承)、protected(保护继承)和private(私有继承)三种,不同的继承方式,导致原来具有不同访问属性的基类成员在派生类中的访间属性也有所不同。这时访问规则有两类:一是派生类中非static成员函数和友元函数对基类成员的访问;二是在派生类作用域外的对象对基类成员的访问。,80,2派生类对象的构造与析构的顺序构造派生类的对象时,就要对基类数据成员、派生类自身的数据成员和对象成员进行初始化。由于基类的构造函数不能被继承下来,要完成这些工作,就必须给派生类添加新的构造函数。派生类构造函数执行的一般顺序如下:(1)调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右)。(2)调用对象成员的构造函数,调用顺序按照它们在类中声明的顺序。(3)执行派生类的构造函数体中的内容。,81,3继承中的基类成员的访问和赋值兼容性当多个基类中定义有同名成员则派生类对这些同名成员的访问可能存在冲突和二义性这时可采用成员名限定法来这种访问不明去的问题。当在多重继承的情况下调用不同基类中的相同成员时可能也会出现二义性问题。C语言规定,在派生类中重新声明的成员函数具有比基类同名成员函数更小的作用域,这时可以在可能出现二义性的地方,加上类名限定,就可避免出现名字冲突问题。,82,赋值兼容规则是指在需要基类对象的任何地方都可以使用公有派生类的对象来替代。通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员。这样,公有派生类实际就具备了基类的所有功能,凡是基类能解决的问题公有派生类都可以解决。赋值兼容规则中所指的替代包括以下的情况:(1)派生类的对象可以赋值给基类的对象(2)派生类的对象可以初始化基类的引用。(3)派生类对象的地址可以赋给指向基类的指针。,83,4虚基类当某类的部分或全部直接基类是从另一个共同基类派生而来时,在这些直接基类中从上一级共同基类继承来的成员就拥有相同的名称。在派生类对象中这些同名数据成员在内存中同时拥有多个副本,同一个函数名会有多个映射。这时可以将共同基类设置为虚基类,那么从不同的路径继承过来的同名数据成员在内存中就只有一个副本,同一个函数名也只有一个映射。因而虚基类解决了同名成员的唯一标识问题。,84,第二部分面向对象的程序设计,第3章类和对象(一)第4章类和对象(二)第5章继承和派生第6章虚函数与多态性第7章运算符重载第8章模板第9章标准模板库STL第10章C+语言的输入和输出,85,第6章虚函数与多态性,本章要点:多态性的概念虚函数的定义与应用多继承与虚函数纯虚函数与抽象类,86,多态性(Polymorphism)是面向对象程序设计的一个非常重要的特性,如果不支持多态性,C+就不是真正的面向对象程序设计语言。多态性指的是不同的对象对于同样的消息会产生不同的行为,而消息在C+语言中指的就是函数的调用,不同的函数可以具有多种不同的功能,而多态就是允许用一个函数名的调用来执行不同的功能。,87,6.1多态性概述,6.1.1多态的类型多态性不仅限于C+语言,从面向对象技术的角度来看,多态性可以分为四类:(1)重载多态,前面学习的函数重载就属于此概念,运算符重载也是重载多态(第7章将详细介绍)。(2)强制多态,指将一个变量类型加以变化,以符合一个函数或者操作的要求,例如加法运算符在进行浮点数与整型数相加时,首先要对整型数进行强制类型转换为浮点数再相加的情况,就是强制多态的实例。(3)包含多态,同样的操作可用于一个类型及其子类型。包含多态一般需要进行运行时的类型检查,主要是通过虚函数来实现。(4)参数多态,采用参数化模板,通过给出不同的类型参数,使得一个结构可以适用多种数据类型,C+提供的函数模板和类模板即为典型的参数多态(第8章将详细介绍)。,88,对于多态性,一个要解决的主要问题就是何时把具体的操作和对象进行绑定(binding),也称联编、关联,绑定也指的是程序如何为类的对象找到执行操作函数的程序入口的过程。从系统实现的角度来看,多态可以分为两类:编译时多态运行时多态,89,编译时多态,指的在程序编译过程中时决定同名操作与对象的绑定关系,也称静态绑定、静态联编,典型的技术有函数重载、运算符重载、模板。由于这种方式是在程序运行前就确定了对象要调用的具体函数,因此程序运行的时候函数调用速度快、效率较高。其缺点是编程不够灵活。,90,运行时多态,指的是在程序运行过程中动态地确定同名操作与具体对象的绑定关系,也称动态绑定、动态联编等,主要通过使用继承和虚函数来实现。在编译、连接过程中确定绑定关系,程序运行之后才能确定。动态绑定的优点是编程更加灵活、系统易于扩展。由于内部增加了实现虚函数调用的机制,因此要比静态绑定的函数调用速度慢些。,91,6.1.2基类指针指向派生类对象,【例6.1】函数重载在多态性中的应用。/*06_01.cpp*/#includeusingnamespacestd;classBase/基类public:voidPrint()cout”BaseClassPrint.”endl;,92,classDerived:publicBase/公有派生类public:voidPrint()coutPrint();pb=,93,BaseClassPrint.DerivedClassPrint.BaseClassPrint.BaseClassPrint.BaseClassPrint.,程序的运行结果为:,94,在派生类中重写了成员函数Print(),使得不同的对象对同一函数名的调用产生了不同的结果,这就是多态性的具体表现,不过却是编译时多态。在程序中可以看到,要想输出对应的字符串,必须明确地指出调用哪个对象的成员函数,即通过类的对象调用,或者通过加类名限定进行调用,如例6.l中的语句:bl.Print();/调用基类对象b1的Print函数d1.Print();/调用派生类对象d1的Print函数d1.Base:Print();/调用派生类对象d1中的继承基类的Print()函数,95,这样调用成员函数的方法是显示调用,对于结构复杂的程序,每个对象都显式地写出要调用的成员函数是不切实际的,往往需要通过对象指针或引用来实现对成员函数的调用。程序结果可以看出,在例6.1中定义了一个基类的指针pb,当把这个指针指向派生类的对象时,希望调用派生类对象d1的Print()函数,但却仍然调用了d1对象包含的基类成员函数Print()。即本程序希望运行结果为:BaseClassPrint.SubClassPrint.BaseClassPrint.BaseClassPrint.SubClassPrint.,96,6.2虚函数,6.2.1虚函数的定义格式虚函数必须存在于类的继承环境之中才有意义,声明虚函数的方法很简单,只要在基类的成员函数名前加关键字virtual即可,格式如下:class类名virtual类型成员函数名(参数表);,97,定义虚函数要注意以下的问题:(1)虚函数必须声明为类的成员函数,全局函数及静态成员函数不能声明为虚函数;(2)虚函数与一般成员函数一样,可定义在类体内,也可以定义在类体外;(3)虚函数的声明只能出现在类函数声明语句中,而不能在成员函数实现的部分声明;(4)构造函数不能声明为虚函数;(5)析构函数可以是虚函数;(6)当一个基类中声明了虚函数,则虚函数特性会在其直接派生类和间接派生类中一直保持下去,并且其派生类不必再用virtual关键字声明。,98,【例6.2】虚函数的定义与应用(对例6.1的改进)。,/*06_02.cpp*/#includeusingnamespacestd;classBase/基类public:virtualvoidPrint()cout”BaseClassPrint.”endl;classDerived1:publicBase/公有派生类1public:voidPrint()cout”Derived1ClassPrint.”endl;,99,classDerived2:publicBase/公有派生类2public:voidPrint()coutPrint();/用指针调用成员函数rb.Print();/用基类引用调用基类对象成员函数pb=,100,BaseClassPrint.BaseClassPrint.Derived1ClassPrint.Derived2ClassPrint.Derived1ClassPrint.,程序的运行结果为:,101,6.2.2多继承与虚函数,前面的内容介绍了在一个基类中定义虚函数,然后定义派生类的使用情况,那么在C+多继承机制当中,虚函数问题该如何处理呢?见例6.3中的程序:,102,【例6.3】多继承中虚函数的定义与应用。,/*06_03.cpp*/#includeusingnamespacestd;classBase1public:virtualvoidTestA()cout”Base1TestA()”endl;classBase2public:virtualvoidTestB()cout”Base2TestB()”TestB();/调用类Derived的TestB()函数return0;,104,DerivedTestA()DerivedTestB(),程序的运行结果为:,105,图6.1多继承与虚函数,106,【例6.4】多继承基类中有同名虚函数。,/*06_04.cpp*/#includeusingnamespacestd;classBase1public:virtualvoidTest()cout”Base1Test()”endl;classBase2public:virtualvoidTest()cout”Base2Test()”Test();/用基类指针pB2调用类Derived的TestB()函数return0;,113,DerivedTestA()DerivedTestB(),程序的运行结果为:,114,图6.2中间类的作用,115,6.2.3虚析构函数,析构函数的作用是在对象撤销之前做必要的“清理现场”的工作。当派生类的对象从内存中撤销时一般先调用派生类的析构函数,然后再调用基类的析构函数。但是,如果用new运算符动态生成一个派生类的堆对象,并让基类指针指向该派生类对象。当程序用delete运算符通过基类指针删除派生类对象时,会发生一种情况:系统会只执行基类的析构函数,而不执行派生类的析构函数。,116,【例6.6】基类中有非虚析构函数时的执行情况。,/*06_06.cpp*/#includeusingnamespacestd;clas

温馨提示

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

评论

0/150

提交评论