《C++程序设计-北京林业大学7%-第07章 继承和派生.ppt》由会员分享,可在线阅读,更多相关《C++程序设计-北京林业大学7%-第07章 继承和派生.ppt(73页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、第七章第七章继承和派生 本章主要内容本章主要内容(1)理解继承的概念和意义。(2)理解单一继承、多重继承中基类与派生类之间的关系。(3)理解并掌握派生类构造函数的编写要求以及派生类对象的构造过程和机理。(4)掌握虚函数和多态性的概念。(5)掌握虚函数的定义方法、调用方法及其在实现多态性方面所起到的作用。(6)了解纯虚函数与抽象基类的概念。2北京林业大学北京林业大学软件教研室软件教研室7.1继承和派生的概念继承和派生的概念l继承(Inheritance)就是在一个已存在的类的基础上建立一个新类,实质就是利用已有的数据类型定义出新的数据类型。l在继承关系中:被继承的类称为基类(被继承的类称为基类(
2、Baseclass)(或父类)(或父类)定义出来的新类称为派生类(定义出来的新类称为派生类(Derivedclass)(子类)(子类)3北京林业大学北京林业大学软件教研室软件教研室l派生类不仅可以继承原来类的成员,还可以通过以下方式扩充新的成员:(1)增加新的数据成员)增加新的数据成员(2)增加新的成员函数)增加新的成员函数(3)重新定义已有成员函数)重新定义已有成员函数(4)改变现有成员的属性)改变现有成员的属性4北京林业大学北京林业大学软件教研室软件教研室多层次继承:在派生过程中,派生出来的新类同样可以作为基类再继续在派生过程中,派生出来的新类同样可以作为基类再继续派生出更新的类,依此类推
3、形成一个层次结构。派生出更新的类,依此类推形成一个层次结构。直接参与派生出某类称为直接参与派生出某类称为直接基类直接基类;而基类的基类,以及更深层的基类称为而基类的基类,以及更深层的基类称为间接基类间接基类。在类的层次结构中,处于高层的类表示最一般的特征,而在类的层次结构中,处于高层的类表示最一般的特征,而处于底层的类则表示更具体的特征。处于底层的类则表示更具体的特征。类族:同时一个基类可以直接派生出多个派生类。同时一个基类可以直接派生出多个派生类。这样形成了一个相互关联的这样形成了一个相互关联的类族类族。如,如,MFC就是这样的类族,它由一个就是这样的类族,它由一个CObject类派生出类派
4、生出200个个MFC类中的绝大多数。类中的绝大多数。5北京林业大学北京林业大学软件教研室软件教研室基类基类1基类基类2基类基类n派生类派生类1派生类派生类2多重继承多重继承 单继承:派生类只有一个直接基类。多重继承:派生类同时有多个直接基类。基类基类派生类派生类1派生类派生类2单继承单继承 6北京林业大学北京林业大学软件教研室软件教研室7.2单继承单继承l7.2.1 单继承的定义方式 class class 派生类名派生类名:访问方式访问方式 基类名基类名 派生类中的新成员派生类中的新成员 7北京林业大学北京林业大学软件教研室软件教研室l【例7-1】类Build_1是一个关于楼房数据的类。它的
5、数据成员有posi_x、posi_y和area,分别表示楼房位置的经、纬度和建筑面积。它的成员函数只有set1,用于设置数据成员posi_x、posi_y和area的值。让Build_1作为基类,再增加数据成员height、成员函数set2和show来定义派生类Build_2。8北京林业大学北京林业大学软件教研室软件教研室7.2.2派生类的成员构成派生类的成员构成l派生新类经历了三个步骤:(1)吸收基类成员)吸收基类成员l派生类继承和吸收了基类的全部数据成员和除了构造派生类继承和吸收了基类的全部数据成员和除了构造函数、析构函数之外的全部成员函数。函数、析构函数之外的全部成员函数。(2)改造基类
6、成员)改造基类成员l一是基类成员的访问方式问题;一是基类成员的访问方式问题;l二是对基类数据成员或成员函数的覆盖。二是对基类数据成员或成员函数的覆盖。(3)添加新成员)添加新成员l保证了派生类在功能上比基类有所发展。保证了派生类在功能上比基类有所发展。9北京林业大学北京林业大学软件教研室软件教研室7.2.3继承方式对基类成员的访问属性控制继承方式对基类成员的访问属性控制l1.公有继承(public继承方式)基类中基类中public和和protected成员的访问属性在派成员的访问属性在派生类中不变;生类中不变;而基类中的不可访问成员和而基类中的不可访问成员和private成员在派生类成员在派生
7、类中不可访问。中不可访问。l注意:不可访问成员与私有成员的区别。10北京林业大学北京林业大学软件教研室软件教研室l2.私有继承(private继承方式)基类中基类中public和和protected成员都以成员都以private成成员出现在派生类中;员出现在派生类中;而基类中的不可访问成员和而基类中的不可访问成员和private成员在派生成员在派生类中不可访问。类中不可访问。l相当于中止了基类功能的继续派生!11北京林业大学北京林业大学软件教研室软件教研室l3.保护继承(protected继承方式)基类中基类中public和和protected成员都以成员都以protected成员出现在派生类
8、中;成员出现在派生类中;而基类中的不可访问成员和而基类中的不可访问成员和private成员在派生成员在派生类中不可访问。类中不可访问。12北京林业大学北京林业大学软件教研室软件教研室类的继承方式对基类成员的访问属性控制 基类成员基类成员作为派生类成员作为派生类成员公有继承公有继承保护继承保护继承私有继承私有继承public成员成员public成员成员protected成员成员private成员成员protected成员成员protected成员成员protected成员成员private成员成员private成员成员不可访问不可访问不可访问不可访问不可访问不可访问不可访问成员不可访问成员不可访
9、问不可访问不可访问不可访问不可访问不可访问13北京林业大学北京林业大学软件教研室软件教研室【例7-2】验证公有继承方式下,类成员的访问特性。14北京林业大学北京林业大学软件教研室软件教研室【例7-3】验证私有继承方式下,类成员的访问特性。15北京林业大学北京林业大学软件教研室软件教研室【例7-4】验证保护继承方式下,类成员的访问特性。16北京林业大学北京林业大学软件教研室软件教研室7.2.4派生类的构造函数派生类的构造函数l派生类的构造函数:一方面负责调用基类的构造函数对基类成员进行初一方面负责调用基类的构造函数对基类成员进行初始化;始化;另一方面还要负责对基类的构造函数所需要的参数另一方面还
10、要负责对基类的构造函数所需要的参数进行必要的设置。进行必要的设置。派生类名派生类名:派生类构造函数名(总参数列表)派生类构造函数名(总参数列表):基类构造函数名(参数列表)基类构造函数名(参数列表)派生类中新增数据成员初始化语句派生类中新增数据成员初始化语句 17北京林业大学北京林业大学软件教研室软件教研室l在定义派生类的对象时:系统首先执行基类的构造函数;系统首先执行基类的构造函数;然后执行派生类的构造函数。然后执行派生类的构造函数。l【例7-5】定义一个点类Point,并由此派生出一个圆类Circle,并测试派生类的构造函数和基类的构造函数的执行顺序。18北京林业大学北京林业大学软件教研室
11、软件教研室l派生类对基类成员重新定义:通过派生类的对象调用一个被重新定义过的基类通过派生类的对象调用一个被重新定义过的基类的成员函数,被调用的是派生类的成员函数;的成员函数,被调用的是派生类的成员函数;此时,若想调用基类的同名成员函数,必须在成此时,若想调用基类的同名成员函数,必须在成员函数名前加基类名和作用域运算符员函数名前加基类名和作用域运算符“:”。l【例7-6】本例中分别定义一个描述圆的类Ccircle和一个描述圆柱体的类Ccylinder。19北京林业大学北京林业大学软件教研室软件教研室l在C+中,处理同名函数时有以下3种基本方法:根据函数参数的特征进行区分,即编译器根据参数的类根据
12、函数参数的特征进行区分,即编译器根据参数的类型或个数进行区分。如:型或个数进行区分。如:max(int,int)max(float,float)根据类对象进行区分。如:在上例中的根据类对象进行区分。如:在上例中的main函数中,函数中,cylinder.area()circle.area()使用作用域运算符使用作用域运算符“:”进行区分,如:进行区分,如:Ccircle:area()l以上3种方法都是在程序编译过程中完成的,因此称为静态联编。20北京林业大学北京林业大学软件教研室软件教研室7.2.5有子对象的派生类的构造函数有子对象的派生类的构造函数派生类名派生类名:派生类构造函数名派生类构造
13、函数名(总参数列表总参数列表):基类构造函数名基类构造函数名(参数列表参数列表),),子对象名子对象名(参数列表参数列表),),派生类中新增数据成员初始化语句派生类中新增数据成员初始化语句 l此时,构造函数执行的一般次序为:调用基类的构造函数。调用基类的构造函数。调用子对象的构造函数。当派生类中含有多个子对象时,调用子对象的构造函数。当派生类中含有多个子对象时,各子对象的构造函数的调用顺序按照它们在类中说明的先各子对象的构造函数的调用顺序按照它们在类中说明的先后顺序进行。后顺序进行。执行派生类构造函数的函数体。执行派生类构造函数的函数体。21北京林业大学北京林业大学软件教研室软件教研室7.2.
14、6派生类的析构函数派生类的析构函数l析构函数的作用是在对象撤销之前,进行必要的清理工作。l当对象被删除时,系统会自动调用析构函数。l析构函数的调用顺序与构造函数的调用顺序正好相反:先执行派生类自己的析构函数;先执行派生类自己的析构函数;然后调用子对象的析构函数;然后调用子对象的析构函数;最后调用基类的析构函数。最后调用基类的析构函数。l【例7-7】分析以下程序的执行结果。22北京林业大学北京林业大学软件教研室软件教研室练习l类有两种用法:一种是类的实例化,即生成类的对象;另一种是通过_,派生出新的类。l如果类A继承了类B,则类A称为_,类B称为_。23北京林业大学北京林业大学软件教研室软件教研
15、室判断题l在派生类的构造函数的初始化表中不能包含对基类的子对象的初始化。l一个派生类还可以作为另一个类的派生类。l派生类至少有一个基类。l派生类中继承的基类的成员的访问权限到派生类保持不变。l派生类的对象可以对它的基类成员中公有继承的保护成员进行访问。l派生类的构造函数的执行顺序是:对象成员的构造函数、基类的构造函数、派生类本身的构造函数。l基类的私有成员不能为派生类的成员所访问。l基类的公有成员在私有继承时在派生类中成为私有成员,而在保护继承时在派生类中成为保护成员。l在公有和保护继承方式下,派生类的成员可以对基类的保护成员进行访问。l在公有和保护继承方式下,派生类的对象可以对基类的保护成员
16、进行访问。24北京林业大学北京林业大学软件教研室软件教研室读程序读程序#includeclassBasepublic:Base()cout执行基类构造函数执行基类构造函数endl;Base()cout执行基类析构函数执行基类析构函数endl;classDerive:publicBasepublic:Derive()cout执行派生类构造函数执行派生类构造函数endl;Derive()cout执行派生类析构函数执行派生类析构函数endl;voidmain()Derived;执行基类构造函数执行基类构造函数执行派生类构造函数执行派生类构造函数执行派生类析构函数执行派生类析构函数执行基类析构函数执行
17、基类析构函数25北京林业大学北京林业大学软件教研室软件教研室7.3多重继承多重继承l7.3.1 多重继承的定义方式 class class 派生类名派生类名:访问方式访问方式 基类名基类名1 1,访问方式访问方式 基类名基类名2 2,;26北京林业大学北京林业大学软件教研室软件教研室l多重继承下,派生类的构造函数的定义格式:派生类构造函数名派生类构造函数名(参数表参数表):):基类名基类名1(1(参数表参数表1)1),基类名基类名2(2(参数表参数表2),2),l在多重继承下,系统首先执行各基类的构造函数,然后再执行派生类的构造函数;l处于同一层次的各基类构造函数的执行顺序与声明派生类时所指定
18、的各基类顺序一致,而与派生类的构造函数定义中所调用的基类构造函数的顺序无关。l【例7-8】测试在多重继承关系下基类和派生类的构造函数的执行顺序。27北京林业大学北京林业大学软件教研室软件教研室练习lC+提供的_机制允许一个派生类继承多个基类,即使这些基类是相互无关的。l在多重继承的构造函数定义中,几个基类的构造函数之间用_分隔。多重继承多重继承逗号逗号28北京林业大学北京林业大学软件教研室软件教研室读程序#includeclassApublic:A(char*s)coutsendl;classB:publicApublic:B(char*s1,char*s2):A(s1)couts2endl;
19、classC:publicApublic:C(char*s1,char*s2):A(s1)couts2endl;classD:publicB,Cpublic:D(char*s1,char*s2,char*s3,char*s4):B(s1,s2),C(s3,s4)couts4endl;voidmain()Dd(classA,classB,classC,classD);classAclassBclassCclassDclassD29北京林业大学北京林业大学软件教研室软件教研室7.3.2多重继承的二义性多重继承的二义性l多重继承下,可能会产生一个类是通过多条路径从一个给定的类中派生出来的情况。通过派
20、生类通过派生类D3D3的对象访问类的对象访问类B B的成员?的成员?BD1D2D330北京林业大学北京林业大学软件教研室软件教研室1多重继承的二义性 情况一:被继承的多个基类中具有同名成员,在情况一:被继承的多个基类中具有同名成员,在派生类中对该同名成员的访问会产生二义性。派生类中对该同名成员的访问会产生二义性。情况二:被继承的多个基类有一个共同的基类,情况二:被继承的多个基类有一个共同的基类,在派生类中访问这个共同基类的成员会产生二义在派生类中访问这个共同基类的成员会产生二义性。性。31北京林业大学北京林业大学软件教研室软件教研室l2多重继承的二义性问题的解决方法 一是使用作用域。一是使用作
21、用域。二是将直接基类的共同基类设置为虚基类。二是将直接基类的共同基类设置为虚基类。直接基类名直接基类名:数据成员名数据成员名 直接基类名直接基类名:成员函数名(参数表)成员函数名(参数表)32北京林业大学北京林业大学软件教研室软件教研室l【例7-9】阅读以下程序,分析程序中的错误及产生的原因。33北京林业大学北京林业大学软件教研室软件教研室7.3.3虚基类及其派生类的构造函数虚基类及其派生类的构造函数l虚基类的声明是在定义派生类时完成:class class 派生类名派生类名:virtual virtual 访问方式访问方式 基类名基类名 /声明派生类成员声明派生类成员 ;l虚基类:虽然被一个
22、派生类间接地多次继承,但派生类却只继承一份该基类的成员。l对于虚基类的任何派生类,其构造函数不仅负责调用直接基类的构造函数,还需调用虚基类的构造函数。34北京林业大学北京林业大学软件教研室软件教研室l若基类B被声明为虚基类,则:派生类派生类D3负责调用负责调用3个基类(直个基类(直接基类接基类D1、D2和虚基类和虚基类B)的)的构造函数;构造函数;而派生类而派生类D1和和D2不会调用虚基不会调用虚基类类B的构造函数,只由最终端的的构造函数,只由最终端的派生类派生类D3负责调用虚基类的构负责调用虚基类的构造函数。造函数。【例7-10】按照如上图所示的类和它们之间的继承与派生关系,设计对应的类和相
23、关的构造函数,将类B声明为虚基类,并测试虚基类的作用,注意虚基类构造函数的执行次数。BD1D2D335北京林业大学北京林业大学软件教研室软件教研室36北京林业大学北京林业大学软件教研室软件教研室解决二义性的两种方法比较解决二义性的两种方法比较l作用域运算符:在派生类中拥有同名成员的多个拷贝,分别通过在派生类中拥有同名成员的多个拷贝,分别通过直接基类名来惟一标识同名成员,可以存放不同直接基类名来惟一标识同名成员,可以存放不同的数据,进行不同的操作。的数据,进行不同的操作。可以容纳更多的数据。可以容纳更多的数据。l虚基类:只维护一个成员拷贝。只维护一个成员拷贝。使用更为简洁,内存空间更为节省。使用
24、更为简洁,内存空间更为节省。37北京林业大学北京林业大学软件教研室软件教研室读程序#includeclassApublic:A(char*s)coutsendl;classB:virtualpublicApublic:B(char*s1,char*s2):A(s1)couts2endl;classC:virtualpublicApublic:C(char*s1,char*s2):A(s1)couts2endl;classD:publicB,Cpublic:D(char*s1,char*s2,char*s3,char*s4):B(s1,s2),C(s3,s4),A(s1)couts4endl;v
25、oidmain()Dd(classA,classB,classC,classD);classAclassBclassDclassD去掉该去掉该virtual:class Aclass Aclass Bclass Dclass D去掉该去掉该virtual:class Aclass Bclass Cclass Dclass D38北京林业大学北京林业大学软件教研室软件教研室7.4虚函数与多态性虚函数与多态性l多态就是考虑在不同层次的类中,以及在一个类的内部,同名成员函数之间的关系问题,是解决功能和行为的再抽象问题。l也就是说,多态是指类族中具有相似功能的不同函数使用同一个名称来实现,从而可以使用
26、相同的调用方式来调用这些具有不同功能的同名函数。39北京林业大学北京林业大学软件教研室软件教研室7.4.1多态性多态性l多态性就是指同样的消息被类的不同对象接收时导致的完全不同的行为的一种现象。l这里所说的消息即对类的成员函数的调用,不同的行为是指不同的实现,也就是调用了不同的函数。l多态性实质是指同一个函数的多种形态。40北京林业大学北京林业大学软件教研室软件教研室l联编:是指把一个消息和一个方法联系在一起是指把一个消息和一个方法联系在一起也就是把一个函数名与其实现代码联系在一起也就是把一个函数名与其实现代码联系在一起实质是把一个标识符名和一个存储地址联系在一起的过程。实质是把一个标识符名和
27、一个存储地址联系在一起的过程。l根据实现联编的阶段的不同,可将其分为静态联编和动态联编两种。l这两种联编过程分别对应着多态的两种实现方式。在编译时的多态是通过静态联编实现的在编译时的多态是通过静态联编实现的而在运行时的多态则是通过动态联编实现的。而在运行时的多态则是通过动态联编实现的。l普通函数及类的成员函数的重载就实现了一种多态性。41北京林业大学北京林业大学软件教研室软件教研室l在继承与派生的环境中:当通过对象名调用某个成员函数时,只可能是调当通过对象名调用某个成员函数时,只可能是调用对象自身的成员,所以,这种情况可采用用对象自身的成员,所以,这种情况可采用静态静态联编联编实现。实现。当通
28、过基类指针调用成员函数时,只有在运行时当通过基类指针调用成员函数时,只有在运行时才能确定实际操作对象的类,并由此确定应该调才能确定实际操作对象的类,并由此确定应该调用哪个类中的成员函数,这种用哪个类中的成员函数,这种运行时的多态性运行时的多态性是是由对象赋值的兼容规则所引起的。由对象赋值的兼容规则所引起的。42北京林业大学北京林业大学软件教研室软件教研室7.4.2赋值兼容规则赋值兼容规则l赋值兼容规则是指在公有继承情况下,对于某些场合,一个派生类的对象可以作为基类对象来使用,也就是在需要基类对象的任何地方都可以使用公有派生类的对象来替代。43北京林业大学北京林业大学软件教研室软件教研室l赋值兼
29、容规则包括以下三种情况(假设类B为基类,类D为类B的公有派生类):(1)派生类的对象可以赋值给基类对象Dd;Bb;b=d;(2)派生类的对象可以初始化为基类的引用Dd;B&b=d;(3)派生类对象的地址可以赋给指向基类的指针Dd;B*bp=&d;44北京林业大学北京林业大学软件教研室软件教研室基类指针指向公有派生类对象基类指针指向公有派生类对象BaseDerive公有继承公有继承基类成员基类成员新增成员45北京林业大学北京林业大学软件教研室软件教研室l由上述对象赋值兼容规则可知:一个基类的对象可兼容派生类的对象;一个基类的对象可兼容派生类的对象;一个基类的指针可指向派生类的对象;一个基类的指针
30、可指向派生类的对象;一个基类的引用可引用派生类的对象;一个基类的引用可引用派生类的对象;l于是,对于通过基类的对象指针(或引用)对成员函数的调用,编译时无法确定对象的类,而只有在运行时才能确定并由此确定该调用哪个类的成员函数。l一个公有派生类的对象可以提供其基类对象的全部行为(基类的全部接口);l也就是说,在程序中可以把一个公有派生类对象当作其基类对象来处理。l【例7-11】赋值兼容规则的概念及实现示例。46北京林业大学北京林业大学软件教研室软件教研室7.4.3用基类指针指向公有派生类对象用基类指针指向公有派生类对象l基类指针、派生类指针、基类对象和派生类对象四者间有以下4种组合的情况:(1)
31、直接用基类指针指向基类对象。)直接用基类指针指向基类对象。(2)直接用派生类指针指向派生类对象。)直接用派生类指针指向派生类对象。(3)用基类指针引用其派生类对象。用基类指针引用其派生类对象。(4)用派生类指针引用基类对象。)用派生类指针引用基类对象。l【例7-12】基类指针、派生类指针、基类对象和派生类对象4者间组合的使用情况示例。基类指针仅能访问派基类指针仅能访问派生类中的基类部分生类中的基类部分强制类型转换强制类型转换47北京林业大学北京林业大学软件教研室软件教研室引入虚函数l阅读下列程序,并写出相应的执行结果。#include#includeclassPersonpublic:Pers
32、on(char*n)name=newcharstrlen(n)+1;strcpy(name,n);voidprint()coutMynameisname.n;protected:char*name;48北京林业大学北京林业大学软件教研室软件教研室classStudent:publicPersonpublic:Student(char*n,ints):Person(n)score=s;void print()coutMy name is name and myscoreisscore.n;private:intscore;classProfessor:publicPersonpublic:Pro
33、fessor(char*n,char*c):Person(n)course=newcharstrlen(c)+1;strcpy(course,c);void print()coutMy name is name and mycourseiscourseprint();Studenty(zhang,97);p=&y;p-print();Professorz(liu,computer);p=&z;p-print();l程序运行结果为:Mynameiswang.Mynameiszhang.Mynameisliu.程序运行结果为程序运行结果为:My name is wang.My name is zh
34、ang and my score is 97.My name is liu and my course is computer.将将Person类中的类中的void print()virtual void print()50北京林业大学北京林业大学软件教研室软件教研室7.4.4虚函数虚函数 class class 类名类名 virtual virtual 类型类型 函数名(参数表);函数名(参数表);;l说明:(1)虚函数声明只能出现在类声明中的函数原型)虚函数声明只能出现在类声明中的函数原型声明中,而不能在成员函数的函数体实现的时候。声明中,而不能在成员函数的函数体实现的时候。(2)只有类的
35、普通成员函数才能声明为虚函数,)只有类的普通成员函数才能声明为虚函数,全局函数及静态成员函数不能声明为虚函数。全局函数及静态成员函数不能声明为虚函数。51北京林业大学北京林业大学软件教研室软件教研室(3)虚函数可以在一个或多个派生类中被重新定)虚函数可以在一个或多个派生类中被重新定义,因此,它属于函数重载的情况。义,因此,它属于函数重载的情况。它要求在派生类中重新定义时必须与基类中的函它要求在派生类中重新定义时必须与基类中的函数原型数原型完全相同完全相同。这时,无论在派生类的相应成员函数前是否加上这时,无论在派生类的相应成员函数前是否加上关键字关键字virtual,都将其视为虚函数;,都将其视
36、为虚函数;系统会遵循以下规则来判断一个派生类的成员函系统会遵循以下规则来判断一个派生类的成员函数是不是虚函数:数是不是虚函数:该函数是否与基类的虚函数有相同的名称;该函数是否与基类的虚函数有相同的名称;该函数是否与基类的虚函数有相同的参数个数及相同该函数是否与基类的虚函数有相同的参数个数及相同的对应参数类型;的对应参数类型;该函数是否与基类的虚函数有相同的返回值或者满足该函数是否与基类的虚函数有相同的返回值或者满足赋值兼容规则的指针、引用型的返回值。赋值兼容规则的指针、引用型的返回值。52北京林业大学北京林业大学软件教研室软件教研室(4)当一个类的成员函数声明为虚函数后,就可)当一个类的成员函
37、数声明为虚函数后,就可以在该类的(直接或间接)派生类中定义与其基以在该类的(直接或间接)派生类中定义与其基类虚函数原型相同的函数。类虚函数原型相同的函数。这时,当用基类指针指向这些派生类对象时,系这时,当用基类指针指向这些派生类对象时,系统会自动用派生类中的同名函数来代替基类中的统会自动用派生类中的同名函数来代替基类中的虚函数。虚函数。也就是说,当用基类指针指向不同派生类对象时,也就是说,当用基类指针指向不同派生类对象时,系统会在程序运行中根据所指向对象的不同,自系统会在程序运行中根据所指向对象的不同,自动选择适当的成员函数,从而实现了运行时的多动选择适当的成员函数,从而实现了运行时的多态性。
38、态性。l【例7-13】虚函数的定义与应用举例。53北京林业大学北京林业大学软件教研室软件教研室l运行过程中的多态需要满足三个条件:首先类之间应满足赋值兼容规则;首先类之间应满足赋值兼容规则;其次是要声明虚函数;其次是要声明虚函数;第三是要由成员函数来调用或者通过指针、引用第三是要由成员函数来调用或者通过指针、引用来访问虚函数。来访问虚函数。l如果使用对象名来访问虚函数,由于联编在编译过程中就可以进行(静态联编),而无需在运行过程中进行。54北京林业大学北京林业大学软件教研室软件教研室读程序#includeclassbaseprivate:intb;public:base(intx=0)b=x;
39、virtualvoidshow()coutbase:b=bendl;coutbase:show()被调用被调用!endl;classderived:publicbaseprivate:intd;public:derived(inty=-1)d=y;virtualvoidshow()coutderived:d=dendl;coutderived:show()被被调调用用!endl;voidmain()derivedd;void(base:*fp)();fp=base:show;(d.*fp)();derived:d=-1derived:show()被调用被调用!55北京林业大学北京林业大学软件教
40、研室软件教研室读程序#includeclassBasepublic:virtualvoidfun(intx)coutbaseclass,x=xendl;classDerive:publicBasepublic:voidfun(floatx)coutderiveclass,x=xendl;voidglobefun(Base&b)inti=1;b.fun(i);floatf=2.0;b.fun(f);voidmain()Baseb;Derived;globefun(b);globefun(d);base class,x=1base class,x=2base class,x=1 base clas
41、s,x=2 基类基类Base声明了虚函数声明了虚函数fun(),但在派生类中重新定义该函数时,但在派生类中重新定义该函数时,其函数原型与基类中的函数原型不同,所以不能实现多态性。其函数原型与基类中的函数原型不同,所以不能实现多态性。56北京林业大学北京林业大学软件教研室软件教研室7.4.5纯虚函数与抽象类纯虚函数与抽象类1纯虚函数 l在定义一个表达抽象概念的基类时,有时可能会无法给出某些成员函数的具体实现。这时,就可以将这些函数声明为纯虚函数。virtual virtual 函数原型函数原型=0=0;纯虚函数是只在基类中声明虚函数但未给出具体纯虚函数是只在基类中声明虚函数但未给出具体的函数定义
42、体;的函数定义体;它的具体定义放在各派生类中。它的具体定义放在各派生类中。57北京林业大学北京林业大学软件教研室软件教研室2抽象类 声明了纯虚函数的类,称为抽象类。声明了纯虚函数的类,称为抽象类。抽象类的主要作用:通过它为一个类族建立一个公抽象类的主要作用:通过它为一个类族建立一个公共的接口,使它们能够更有效地发挥多态特性。共的接口,使它们能够更有效地发挥多态特性。抽象类声明了一族派生类的共同接口,而接口的完抽象类声明了一族派生类的共同接口,而接口的完整实现,即纯虚函数的函数体,要由派生类自己定整实现,即纯虚函数的函数体,要由派生类自己定义。义。58北京林业大学北京林业大学软件教研室软件教研室
43、3使用纯虚函数与抽象类的注意事项(1)抽象类只能用作基类来派生新类,不能声明抽象类的)抽象类只能用作基类来派生新类,不能声明抽象类的对象,但可以声明指向抽象类的指针变量和引用变量。对象,但可以声明指向抽象类的指针变量和引用变量。(2)抽象类中可以有多个纯虚函数。)抽象类中可以有多个纯虚函数。(3)抽象类中也可以定义其他非纯虚函数。)抽象类中也可以定义其他非纯虚函数。(4)抽象类派生出新类之后,如果在派生类中没有重新定)抽象类派生出新类之后,如果在派生类中没有重新定义基类中的纯虚函数,则必须再将该虚函数声明为纯虚函义基类中的纯虚函数,则必须再将该虚函数声明为纯虚函数,这时,这个派生类仍然是一个抽
44、象类;数,这时,这个派生类仍然是一个抽象类;(5)在一个复杂的类继承结构中,越上层的类抽象程度就)在一个复杂的类继承结构中,越上层的类抽象程度就越高,有时甚至无法给出某些成员函数的具体实现。越高,有时甚至无法给出某些成员函数的具体实现。(6)引入抽象类的目的主要是为了能将相关类组织在一个)引入抽象类的目的主要是为了能将相关类组织在一个类继承结构中,并通过抽象类来为这些相关类提供统一的类继承结构中,并通过抽象类来为这些相关类提供统一的操作接口。操作接口。59北京林业大学北京林业大学软件教研室软件教研室l【例7-14】设计一个抽象类Shape,它表示具有形状的东西,并体现了抽象的概念,在它下面可以
45、派生出多种具体形状,比如三角形、矩形等。60北京林业大学北京林业大学软件教研室软件教研室选择题1.下列对派生类的描述中,错误的是()。A)一个派生类可以作为另一个派生类的基类一个派生类可以作为另一个派生类的基类B)派生类至少有一个基类派生类至少有一个基类C)派生类的缺省继承方式是派生类的缺省继承方式是privateD)派生类只继承了基类的公有成员和保护成员派生类只继承了基类的公有成员和保护成员2.下列说法中错误的是()。A)公有继承时基类中的公有继承时基类中的public成员在派生类中仍是成员在派生类中仍是public的的B)公有继承时基类中的公有继承时基类中的private成员在派生类中仍是
46、成员在派生类中仍是private的的C)私有继承时基类中的私有继承时基类中的public成员在派生类中是成员在派生类中是private的的D)保护继承时基类中的保护继承时基类中的public成员在派生类中是成员在派生类中是protected的的3.下列虚基类的声明中正确的是()。A)classvirtualB:publicAB)virtualclassB:publicAC)classB:publicAvirtualD)classB:virtualpublicA61北京林业大学北京林业大学软件教研室软件教研室填空题(1)面向对象程序设计的3大机制为:、。(2)可以把派生类的对象当作基类对象来处理
47、,这是 的概念。(3)声明虚函数的方法是在基类中的成员函数原型前加上关键字 。(4)在编译时就解决的函数调用称为 联编;在运行时才解决的函数调用称为 联编。(5)是一个在基类中说明的虚函数,但未给出具体的实现,要求在其派生类实现。(6)在类定义中,将 置于虚函数的函数原型的末尾可以声明该函数为纯虚函数。(7)如果一个类中有一个或多个纯虚函数,则这个类称为 。62北京林业大学北京林业大学软件教研室软件教研室7.5综合实例综合实例l【例7-15】将如下继承关系中的3个类,分别用相应的定义文件(.h)和实现文件(.cpp)来管理。CAnimalCFishCElephant63北京林业大学北京林业大学
48、软件教研室软件教研室类名类名定义文件定义文件实现文件实现文件Animal/Animal.h#include class CAnimalpublic:CAnimal();/Animal.cpp#include Animal.hCAnimal:CAnimal()coutAnimal!endl;Elephant/Elephant.h#include Animal.hclass CElephant:public CAnimalpublic:CElephant();/Elephant.cpp#include Elephant.hCElephant:CElephant()coutElephant!endl
49、;Fish/Fish.h#include Animal.hclass CFish:public CAnimalpublic:CFish();/Fish.cpp#include Fish.hCFish:CFish()coutFish!endl;64北京林业大学北京林业大学软件教研室软件教研室/main.cpp#includeFish.h#includeElephant.hvoidmain()CFishf;CElephante;65北京林业大学北京林业大学软件教研室软件教研室/Animal.h#ifndef_ANIMAL_H_#define_ANIMAL_H_#includeclassCAnima
50、lpublic:CAnimal();#endif66北京林业大学北京林业大学软件教研室软件教研室l【例7-16】设计一个基类Person,包含name和age两个数据成员;由它派生出学生类Student和教师类Teacher,其中学生类添加学号no数据,教师类添加职称title数据;每个类均有构造函数和析构函数;编程实现,并用一些数据进行测试。PersonTeacherStudent(1 1)派生类的有参构造)派生类的有参构造函数中需要为基类的有参函数中需要为基类的有参构造函数提供参数。构造函数提供参数。(2 2)由于在派生类中重)由于在派生类中重新定义了显示函数,若要新定义了显示函数,若要调