第八章 继承与多态-63_第1页
第八章 继承与多态-63_第2页
第八章 继承与多态-63_第3页
第八章 继承与多态-63_第4页
第八章 继承与多态-63_第5页
已阅读5页,还剩102页未读 继续免费阅读

下载本文档

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

文档简介

1、继承(继承(inheritance)机制是面向对象程序设计使代码可以机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。基础上进行扩展,增加功能。这样产生新的类,称派生类。这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构。继承呈现了面向对象程序设计的层次结构。体现了由简单体现了由简单到复杂的认识过程到复杂的认识过程。第八章第八章 继承与多态继承与多态多态性(多态性(polymorphism)多态性是考虑在不同层次的类多态性是考虑在不同层次的类中,以及在同一类中,同名的成员函数

2、之间的关系问题。中,以及在同一类中,同名的成员函数之间的关系问题。函数的重载,运算符的重载,属于编译时的多态性。以虚函数的重载,运算符的重载,属于编译时的多态性。以虚基类为基础的运行时的多态性是面向对象程序设计的标志基类为基础的运行时的多态性是面向对象程序设计的标志性特征。性特征。 体现了类推和比喻的思想方法。体现了类推和比喻的思想方法。 第八章 继承与多态8.1 继承与派生的概念继承与派生的概念 8.4 虚基类虚基类 8.3 多重继承与派生类成员标识多重继承与派生类成员标识 8.6 MFC基础类及其层次结构基础类及其层次结构 8. 8 MFC的消息映射与命令传递的消息映射与命令传递 8. 7

3、 多态性与虚函数多态性与虚函数 8.5 派生类应用讨论派生类应用讨论 8. 9 图书馆流通管理系统设计图书馆流通管理系统设计 继承与多态的应用继承与多态的应用 8.2 派生类的构造函数与析构函数派生类的构造函数与析构函数 8.1 继承与派生的概念层次概念层次概念是计算机的重要概念。通过是计算机的重要概念。通过继承继承(inheritance)的机制可对类(的机制可对类(class)分层,提供类型)分层,提供类型/子类型的关系。子类型的关系。C+通过通过类派生类派生(class derivation)的机制来支持继承。被)的机制来支持继承。被继承的类称为继承的类称为基类基类(base class

4、)或)或超类超类(superclass),新),新产生的类为产生的类为派生类派生类(derived class)或)或子类子类(subclass)。)。基类和派生类的集合称作基类和派生类的集合称作类继承层次结构类继承层次结构(hierarchy)。)。 如果基类和派生类共享相同的公有接口,则派生类被称作如果基类和派生类共享相同的公有接口,则派生类被称作基类的子类型(基类的子类型(subtype)。)。 派生派生反映了事物之间的联系,事物的共性与个性之间的关反映了事物之间的联系,事物的共性与个性之间的关系。系。 派生与独立设计若干相关的类,派生与独立设计若干相关的类,前者工作量少,重复的部前者工

5、作量少,重复的部分可以从基类继承来,不需要单独分可以从基类继承来,不需要单独编程编程。 8.1 继承与派生的概念8.1.1 类的派生与继承类的派生与继承 8. 1.2 公有派生与私有派生公有派生与私有派生 由基类派生出派生类的定义的一般形式为由基类派生出派生类的定义的一般形式为class 派生类名:访问限定符派生类名:访问限定符 基类名基类名1,访问限定符,访问限定符 基类名基类名2,访问限定符,访问限定符 基类名基类名n private: 成员表成员表1; /派生类增加或替代的私有成员派生类增加或替代的私有成员public:成员表成员表2; /派生类增加或替代的公有成员派生类增加或替代的公有

6、成员protected:成员表成员表3; /派生类增加或替代的保护成员派生类增加或替代的保护成员;/分号不可少分号不可少其中基类其中基类1 1,基类,基类2 2,是已声明的类。是已声明的类。 在派生类定义的类体中在派生类定义的类体中给出的成员称为给出的成员称为派生类成员派生类成员,它们是新增加成员,它们给派生类,它们是新增加成员,它们给派生类添加了不同于基类的新的属性和功能。派生类成员也包括取代基添加了不同于基类的新的属性和功能。派生类成员也包括取代基类成员的更新成员。类成员的更新成员。8.1.1 8.1.1 类的派生与继承类的派生与继承基类基类1基类基类2基类基类n派生类派生类1派生类派生类

7、2基类基类派生类派生类1派生类派生类2(a)多重继承)多重继承 (b)单继承)单继承 图图8.1 多重继承与单继承多重继承与单继承 一个基类一个基类可以直接可以直接派生出多派生出多个派生类个派生类 派生类可派生类可以由多个以由多个基类共同基类共同派生出来,派生出来,称多重继称多重继承。承。8.1.1 8.1.1 类的派生与继承类的派生与继承如果一个派生类可以同时有多个基类,称为多重继承如果一个派生类可以同时有多个基类,称为多重继承(multiple-inheritance),这时的派生类同时得到了多个),这时的派生类同时得到了多个已有类的特征。一个派生类只有一个直接基类的情况称为单已有类的特征

8、。一个派生类只有一个直接基类的情况称为单一继承(一继承(single-inheritance)。)。编制编制派生派生类时类时可分可分四步四步 吸收基类的成员吸收基类的成员 改造基类成员改造基类成员 发展新成员发展新成员 重写构造函数与析构函数重写构造函数与析构函数 8.1.1 8.1.1 类的派生与继承类的派生与继承不论是数据成员,还是函数成员,不论是数据成员,还是函数成员,除构造函数与析构函数外全盘接收除构造函数与析构函数外全盘接收 声明一个和某基类成员同名的新成员声明一个和某基类成员同名的新成员,派派生类中的新成员就屏蔽了基类同名成员生类中的新成员就屏蔽了基类同名成员称为同名覆盖(称为同名

9、覆盖(override) 派生类新成员必须与基类成员不同名,它派生类新成员必须与基类成员不同名,它的加入保证派生类在功能上有所发展。的加入保证派生类在功能上有所发展。 8.1.1 8.1.1 类的派生与继承类的派生与继承上面的步骤就是继承与派生编程的规范化步骤。上面的步骤就是继承与派生编程的规范化步骤。第二步中,新成员如是成员函数,参数表也必须一样,否第二步中,新成员如是成员函数,参数表也必须一样,否则是重载。则是重载。第三步中,独有的新成员才是继承与派生的核心特征。第三步中,独有的新成员才是继承与派生的核心特征。第四步是重写构造函数与析构函数,派生类不继承这两种第四步是重写构造函数与析构函数

10、,派生类不继承这两种函数。不管原来的函数是否可用函数。不管原来的函数是否可用一律重写可免出错一律重写可免出错。 访问控制访问控制,亦称为,亦称为继承方式继承方式,是对基类成员进一步的,是对基类成员进一步的限制。访问控制也是三种:限制。访问控制也是三种:公有(公有(public)方式,亦称公有继承)方式,亦称公有继承保护(保护(protected)方式,亦称保护继承)方式,亦称保护继承私有(私有(private)方式,)方式, 亦称私有继承。亦称私有继承。 (默认的继承方式默认的继承方式)8.1.2 8.1.2 公有派生与私有派生公有派生与私有派生不可直接访问 不可直接访问 private 不可

11、直接访问 private protected 不可直接访问 private public 私有派生 不可直接访问 不可直接访问 private 不可直接访问 protected protected 可直接访问 public public 公有派生 在派生类对象外访问派生类对象的基类成员 在派生类中对基类成员的访问限定 基类中成员的访问限定 派生方式 访问限定符有两方面含义:访问限定符有两方面含义:派生类成员(新增成员)函数派生类成员(新增成员)函数对基类(继承来的)成员的访问(调用和操作)对基类(继承来的)成员的访问(调用和操作),和,和从从派生类对象之外对派生类对象中的基类成员的访问派生类对

12、象之外对派生类对象中的基类成员的访问。公有派生是绝对主流公有派生是绝对主流。 8.1.2 8.1.2 公有派生与私有派生公有派生与私有派生不可继承Operator()不可继承友邻关系不可直接访问静态成员任何派生方式不可直接访问 不可直接访问 private 不可直接访问 privateprotected 不可直接访问 protectedpublic 保护派生 在派生类对象外访问派生类对象的基类成员 在派生类中对基类成员的访问限定 基类中成员的访问限定 派生方式 基类的私有成员虽然不能继承,但是在子类中保留存储空间基类的私有成员虽然不能继承,但是在子类中保留存储空间派生类派生类的构造函数的定义形

13、式为:的构造函数的定义形式为:派生类名派生类名:派生类名(参数总表)派生类名(参数总表):基类名基类名1(参数表(参数表1),基类名基类名2(参数表(参数表2),),基类名,基类名n(参数表(参数表n),成员对象名成员对象名1(成员对象参数表(成员对象参数表1),),成员对象名,成员对象名m(成员对象参数表(成员对象参数表m)/派生类新增成员的初始化;派生类新增成员的初始化; /所列出的成员对象名全部为新增成员对象的名字所列出的成员对象名全部为新增成员对象的名字 在构造函数的声明中,冒号及冒号以后部分必须略去。在构造函数的声明中,冒号及冒号以后部分必须略去。 所谓不能继承并不是不能利用,而是把

14、基类的构造函数作所谓不能继承并不是不能利用,而是把基类的构造函数作为新的构造函数的一部分,或者讲调用基类的构造函数。为新的构造函数的一部分,或者讲调用基类的构造函数。 冒号后的基类名,成员对象名的次序可以随意,这里的次冒号后的基类名,成员对象名的次序可以随意,这里的次序与调用次序无关。序与调用次序无关。 8.2 8.2 派生类的构造函数与析构函数派生类的构造函数与析构函数派生类构造函数各部分的执行次序为:派生类构造函数各部分的执行次序为: 1.1.调用基类构造函数,按它们在派生类定义调用基类构造函数,按它们在派生类定义的先后顺序,顺序调用。的先后顺序,顺序调用。 2.2.调用成员对象的构造函数

15、,按它们在类定调用成员对象的构造函数,按它们在类定义中声明的先后顺序,顺序调用。义中声明的先后顺序,顺序调用。3.3.派生类的构造函数体中的操作。派生类的构造函数体中的操作。8.2 8.2 派生类的构造函数与析构函数派生类的构造函数与析构函数*在派生类构造函数中,只要基类不是使用缺省构造函数都在派生类构造函数中,只要基类不是使用缺省构造函数都要显式给出基类名和参数表。要显式给出基类名和参数表。如果基类没有定义构造函数,则派生类也可以不定义,全如果基类没有定义构造函数,则派生类也可以不定义,全部采用系统给定的缺省构造函数。部采用系统给定的缺省构造函数。如果基类定义了带有形参表的构造函数时,派生类

16、就应当如果基类定义了带有形参表的构造函数时,派生类就应当定义构造函数。定义构造函数。8.2 8.2 派生类的构造函数与析构函数派生类的构造函数与析构函数 析构函数析构函数的功能是作善后工作。的功能是作善后工作。 只要在函数体内把派生类新增一般成员只要在函数体内把派生类新增一般成员处理好就可以了处理好就可以了,而,而对新增的成员对象和基对新增的成员对象和基类的善后工作,系统会自己调用成员对象和类的善后工作,系统会自己调用成员对象和基类的析构函数来完成基类的析构函数来完成。 析构函数各部分执行次序与构造函数相析构函数各部分执行次序与构造函数相反,反,首先对派生类新增一般成员析构,然后首先对派生类新

17、增一般成员析构,然后对新增对象成员析构,最后对基类成员析构对新增对象成员析构,最后对基类成员析构。【例【例8.18.1】由在册人员类公有派生学生类】由在册人员类公有派生学生类【例【例8.1】由在册人员类公有派生学生类。我】由在册人员类公有派生学生类。我们希望基类和派生类共享相同的公有接口们希望基类和派生类共享相同的公有接口,只只能采用公有派生来实现。能采用公有派生来实现。首先来看基类:首先来看基类:class Personchar IdPerson19; /身份证号身份证号,18位数字位数字char *Name;/姓名姓名Tsex Sex; /性别性别enum Tsexmid,man,woma

18、n;int Birthday; /生日生日,格式格式1986年年8月月18日写作日写作19860818char *HomeAddress;/家庭地址家庭地址public:Person(char *,char *,Tsex,int,char *); /构造函数构造函数 Person(); /缺省的构造函数缺省的构造函数 Person(); /析构函数析构函数【例【例8.18.1】由在册人员类公有派生学生类】由在册人员类公有派生学生类void SetName(char *); /修改名字修改名字char *GetName()return Name; /提取名字提取名字void SetSex(Tse

19、x sex)Sex=sex; /修改性别修改性别Tsex GetSex()return Sex; /提取性别提取性别void SetId(char *id)strcpy(IdPerson,id); /修改身份证号修改身份证号char *GetId()return IdPerson; /提取身份证号提取身份证号void SetBirth(int birthday)Birthday=birthday; /修改生日修改生日int GetBirth()return Birthday; /提取生日提取生日void SetHomeAdd(char *); /修改住址修改住址char *GetHomeAdd

20、()return HomeAddress; /提取住址提取住址void PrintPersonInfo(); /输出个人信息输出个人信息;接口函数:接口函数:【例【例8.18.1】由在册人员类公有派生学生类】由在册人员类公有派生学生类派生的学生类派生的学生类:class Student:public Person /定义派生的学生类定义派生的学生类 char NoStudent10; /学号学号 course cs30; /30门课程与成绩门课程与成绩public: Student(char *id,char *name,Tsex sex,int birthday, char *homeadd

21、,char* nostud);/注意派生类构造函数声明方式注意派生类构造函数声明方式 Student(); /缺省派生类构造函数缺省派生类构造函数 Student(); /派生类析构函数派生类析构函数 SetCourse(char *,int); /课程设置课程设置 int GetCourse(char *); /查找成绩查找成绩 void PrintStudentInfo(); /打印学生情况打印学生情况;struct course char* coursename; int grade;在在VC+平台上运行例平台上运行例8.1验证主函数验证主函数在册人员在册人员学生学生(单继承单继承)教职

22、工教职工(单继承单继承)兼职教师兼职教师(单继承单继承)教师教师(单继承单继承)行政人员行政人员(单继承单继承)工人工人(单继承单继承)研究生研究生(单继承单继承)行政人员兼教师行政人员兼教师(多重继承多重继承)在职研究生在职研究生(多重继承多重继承)研究生助教研究生助教(多重继承多重继承)图图8.2 大学在册人员继承关系大学在册人员继承关系8.3 8.3 多重继承与派生类成员标识多重继承与派生类成员标识由多个基类共同派生出新的派生类,这样的继承结构由多个基类共同派生出新的派生类,这样的继承结构被称为多重继承或多继承(被称为多重继承或多继承(multiple-inheritance) 派生出来

23、派生出来的新类同的新类同样可以作样可以作为基类再为基类再继续派生继续派生出更新的出更新的类,依此类,依此类推形成类推形成一个一个层次层次结构结构。 椅子椅子床床沙发沙发(单继承单继承)躺椅躺椅(多重继承多重继承)两用沙发两用沙发(多重继承多重继承)图图8.3 椅子,床到两用沙发椅子,床到两用沙发8.3 8.3 多重继承与派生类成员标识多重继承与派生类成员标识歧义性问题歧义性问题:参见图参见图8.2,比如行政人员兼,比如行政人员兼教师,在其基类教师中有一个教师,在其基类教师中有一个“教职工编号教职工编号”,另一基类行,另一基类行政人员中也有一个政人员中也有一个“教职工编教职工编号号”,如果只讲教

24、职工编号那,如果只讲教职工编号那么是哪一个基类中的呢?这两么是哪一个基类中的呢?这两者可能是一回事,但者可能是一回事,但计算机系计算机系统并不这么认为统并不这么认为。进一步,如果进一步,如果“教职工编号教职工编号” 是由两个基类是由两个基类“教师教师”和和“行行政人员政人员”共同的基类共同的基类“教职工教职工”类继承来的,只有同一个标识类继承来的,只有同一个标识符,也不能用改标识符来区分。符,也不能用改标识符来区分。 唯一标识问题唯一标识问题。通常采用作。通常采用作用域分辨符用域分辨符“:”:基类名基类名:成员名成员名;/数据成员数据成员基类名基类名:成员名(参数表)成员名(参数表); /函数

25、成员函数成员class EGStudent int No在职学号在职学号 class GStudent int No研究生号 .class Student int No学生号 . class Person int No身份证号 .class Employee int No工作证号 .class Person int No身份证号 .图图8.4(a)在职研究生派生类关系)在职研究生派生类关系 定义定义EGStudent类对象类对象EGStudent1,并假定派生全部为公有派生,并假定派生全部为公有派生,而而int No全为公有成员全为公有成员:EGStud1.No /在职学号在职学号EGStud

26、1.GStudent:No /研究生号研究生号EGStud1.GStudent.Student:No /学生号学生号 EGStud1.GStudent.Student. Person:No /身份证号身份证号EGStud1.Employee:No /工作证号工作证号EGStud1.Employee.Person:No /身份证号身份证号两个身份证号从逻辑上两个身份证号从逻辑上讲应是一回事讲应是一回事,但是物理但是物理上是分配了不同内存空上是分配了不同内存空间,是两个变量,请参间,是两个变量,请参见图见图8.4(b)。Person Person StudentEmployee GStudent

27、EGStudentPerson成员成员 Person成员成员 Student新成员新成员 GStudent新成员新成员 Employee新成员新成员 EGStudent新成员新成员 图图8.4(b)在职研究)在职研究生派生类存储图生派生类存储图 如果如果class Person的身份证号标识为的身份证号标识为int IdPerson,则可写为:,则可写为:EGStud1.GStudent:IdPersonEGStud1.Employee:IdPerson不必标出那么多层次的类,但写不必标出那么多层次的类,但写EGStud1.IdPerson是错的。是错的。采用有确定字面意思的标识符,可以被编译

28、器简单区分出来。采用有确定字面意思的标识符,可以被编译器简单区分出来。作用域分辨符不能嵌套使用作用域分辨符不能嵌套使用,如:,如:EGStud1.GStudent:Student:No/学生号学生号EGStud1.GStudent:Student:Person:No /身份证号身份证号均是均是错误错误的。的。 8.3 8.3 多重继承与派生类成员标识多重继承与派生类成员标识8.3 8.3 多重继承与派生类成员标识多重继承与派生类成员标识一般数据成员总是私有成员,派生类对基类一般数据成员总是私有成员,派生类对基类的访问只能间接进行。访问身份证号,应通的访问只能间接进行。访问身份证号,应通过过cl

29、ass Personclass Person中的公有成员函数(接口)中的公有成员函数(接口)GetNo()GetNo()和和SetNo()SetNo()进行:进行:EGStud1.Employee.Person:SetNo(int no);EGStud1.Employee.Person:SetNo(int no);no=EGStud1.Employee.Person:GetNo();no=EGStud1.Employee.Person:GetNo();【例【例8.2】由圆和高多重继承派生出圆锥。】由圆和高多重继承派生出圆锥。本例中类本例中类Circle为圆;类为圆;类Line为高;类为高;类C

30、one为圆锥,为圆锥,由由Circle和和Line公有派生而来。在公有派生而来。在Cone类中,类中,Circle和和Line类的接口完全不变,可以直接调用,这就是公有派类的接口完全不变,可以直接调用,这就是公有派生的优点。在生的优点。在Cone的成员函数中可直接访问的成员函数中可直接访问Circle和和Line中的公有成员,但不能直接访问私有成员。中的公有成员,但不能直接访问私有成员。 【例【例8.28.2】由圆和高多重继承派生出圆锥】由圆和高多重继承派生出圆锥检证主程序:检证主程序:圆类圆类Circle定义定义高类高类Line定义定义圆锥类圆锥类Cone定义定义在图在图8.4中,两个身份证

31、号显然是不合理的。可以把中,两个身份证号显然是不合理的。可以把class Person这个共同基类设置为这个共同基类设置为虚基类虚基类,这样从不同,这样从不同路径继承来的同名数据成员在内存中就只有一个拷贝,路径继承来的同名数据成员在内存中就只有一个拷贝,同名函数也只有一种映射。同名函数也只有一种映射。 8.4 8.4 虚基类虚基类virtual 关键字只对紧随其后的基类名起作用关键字只对紧随其后的基类名起作用:class Student:virtual public Person.;class Employee:virtual public Person.;虚基类(虚基类(virtual ba

32、se class)定义方式如下:定义方式如下:class 派生类名派生类名:virtual 访问限定符访问限定符 基类类名基类类名.;class 派生类名派生类名:访问限定符访问限定符 virtual 基类类名基类类名.;8.4 8.4 虚基类虚基类图图8.5 采用虚基类后在职研究生类储存图采用虚基类后在职研究生类储存图StudentGStudentEGStudentPersonStudent新成员新成员GStudent新成员新成员PersonEmployee新成员新成员Person成员成员EGStudent新成员新成员PersonPersonEmployee这种继承称这种继承称为虚拟继承为虚

33、拟继承在职研究生类的储存图参见图在职研究生类的储存图参见图8.5: 在在Person的位置上放的是指针的位置上放的是指针,两个指针都指向两个指针都指向Person成员存储的内存成员存储的内存。这种继承称为虚拟继承。这种继承称为虚拟继承(virtual inheritance)。)。8.4 8.4 虚基类虚基类在派生类对象的在派生类对象的创建创建中,首先是中,首先是虚基类的构造函数虚基类的构造函数并按它们并按它们声明的顺序构造。第二批声明的顺序构造。第二批是非虚基类的构造函数是非虚基类的构造函数按它们声明按它们声明的顺序调用。第三批是的顺序调用。第三批是成员对象的构造函数成员对象的构造函数。最后

34、是。最后是派生类派生类自己的构造函数自己的构造函数被调用被调用【例8.3】在采用虚基类的多重继承中,构造与析构的次序。在采用虚基类的多重继承中,构造与析构的次序。class Dclass:public Bclass1,virtual Bclass3,virtual Bclass2 Object object;public: Dclass():object(),Bclass2(),Bclass3(),Bclass1() cout派生类建立!n; Dclass()cout派生类析构!n;void main() Dclass dd; coutPrintStudentInfo();per4=stu4;

35、delete per4;/用基类指针撤销派生类用基类指针撤销派生类,动态生成的对象必须显式撤销,动态生成的对象必须显式撤销8.7.1 8.7.1 虚函数的定义虚函数的定义在主函数中添加以下内容:在主函数中添加以下内容: 通过在析构函数中加显示语句发现先调通过在析构函数中加显示语句发现先调Student析构函析构函数,后调数,后调Person析构函数。析构函数。 这里再次强调这里再次强调动态生成的对象必须显式撤销动态生成的对象必须显式撤销。在在VC+平台上运行例平台上运行例8.5_1。纯虚函数纯虚函数(pure virtual function)是指被标)是指被标明为不具体实现的虚拟成员函数。它

36、用于这明为不具体实现的虚拟成员函数。它用于这样的情况:定义一个基类时,会遇到无法定样的情况:定义一个基类时,会遇到无法定义基类中虚函数的具体实现,其实现依赖于义基类中虚函数的具体实现,其实现依赖于不同的派生类。不同的派生类。8.7.2 8.7.2 纯虚函数纯虚函数定义纯虚函数的一般格式为:定义纯虚函数的一般格式为:virtual 返回类型返回类型 函数名(参数表)函数名(参数表)=0;含有纯虚函数的基类是不能用来定义对象的。纯虚含有纯虚函数的基类是不能用来定义对象的。纯虚函数没有实现部分,不能产生对象,所以含有纯虚函数没有实现部分,不能产生对象,所以含有纯虚函数的类是抽象类。函数的类是抽象类。

37、1 定义纯虚函数时,不能定义虚函数的实现部分。定义纯虚函数时,不能定义虚函数的实现部分。即使是函数体为空也不可以,函数体为空就可以执即使是函数体为空也不可以,函数体为空就可以执行,只是什么也不做就返回。而纯虚函数不能调用。行,只是什么也不做就返回。而纯虚函数不能调用。2 “=0”表明程序员将不定义该函数,函数声明是表明程序员将不定义该函数,函数声明是为派生类保留一个位置。为派生类保留一个位置。“=0”本质上是将指向函数本质上是将指向函数体的指针定为体的指针定为NULL。3 在派生类中必须有重新定义的纯虚函数的函数在派生类中必须有重新定义的纯虚函数的函数体,这样的派生类才能用来定义对象。体,这样

38、的派生类才能用来定义对象。8.7.2 8.7.2 纯虚函数纯虚函数定义纯虚函数必须注意:定义纯虚函数必须注意:【例【例8.8】学校对在册人员进行奖励,依据是业】学校对在册人员进行奖励,依据是业绩分,但是绩分,但是业绩分的计算方法只能对具体人员进业绩分的计算方法只能对具体人员进行行,如学生,教师,行政人员,工人,算法都不,如学生,教师,行政人员,工人,算法都不同,所以可以将在册人员类作为一个抽象类,同,所以可以将在册人员类作为一个抽象类,业业绩计算方法作为一个纯虚函数绩计算方法作为一个纯虚函数。在主函数中全部用指向基类的指针来调用在主函数中全部用指向基类的指针来调用8.7.2 8.7.2 纯虚函

39、数纯虚函数业绩分业绩分基类定义基类定义业绩分业绩分学生派生类定义学生派生类定义业绩分业绩分教师派生类定义教师派生类定义验证验证主函数主函数【例【例8.9】用用虚函数虚函数来实现来实现辛普生辛普生法求函数的定积分。法求函数的定积分。bannnyyyyyyyyxdxxf)( 2)( 431)(24213108.7.2 8.7.2 纯虚函数纯虚函数纯虚函数实现通用算法纯虚函数实现通用算法:辛普生法求辛普生法求定积分类定积分类在派生类中加在派生类中加被积函数被积函数:验证验证主函数主函数【例【例8.10】通用单链表派生类。】通用单链表派生类。首先首先改造【例改造【例7.4】的头文件,不】的头文件,不采

40、用模板类,而采用采用模板类,而采用虚函数实现多态性,达到通用的目的虚函数实现多态性,达到通用的目的。结点类数据域被。结点类数据域被改造为指针,而把数据放在一个抽象类中,由指针与之改造为指针,而把数据放在一个抽象类中,由指针与之建立联系。建立联系。数据域数据域(指向抽象(指向抽象数据类的指数据类的指针)针)由抽象类派由抽象类派生的数据类生的数据类对象(如串对象(如串对象)对象)指针域(指指针域(指向下一结向下一结点)点)结点类对象结点类对象动态建立的动态建立的数据类对象数据类对象图图8.9 结点构造结点构造class Object /数据类为抽象类数据类为抽象类public: Object()

41、/构造函数不可为虚函数构造函数不可为虚函数 virtual int Compare(Object &)=0; /纯虚函数纯虚函数 virtual void Print()=0; /纯虚函数纯虚函数 virtual Object() ; /析构函数可为虚函数析构函数可为虚函数【例【例8.108.10】通用单链表派生类】通用单链表派生类首先看结点组织,采用结点类加数据类首先看结点组织,采用结点类加数据类数据类定义数据类定义:本题两个要点:本题两个要点:采用虚函数实现多态性,达到通用的目的。采用虚函数实现多态性,达到通用的目的。堆内存的分配与释放,关键不是创建,而是释放!堆内存的分配与释放,

42、关键不是创建,而是释放!数据抽象类中含有两个纯虚函数:比较函数和输出函数据抽象类中含有两个纯虚函数:比较函数和输出函数。当抽象类在派生时重新定义两个纯虚函数,可以数。当抽象类在派生时重新定义两个纯虚函数,可以进行各种类型,包括类和结构对象的比较和输出进行各种类型,包括类和结构对象的比较和输出。本例介绍程序总体组成为主,链本例介绍程序总体组成为主,链表的操作由学生自己表的操作由学生自己仔细仔细阅读。阅读。【例【例8.108.10】通用单链表派生类】通用单链表派生类抽象类中的抽象类中的析构函数也是虚函数,这一点非常重要,析构函数也是虚函数,这一点非常重要,当抽象类派生的数据类的数据部分是动态产生的

43、,当抽象类派生的数据类的数据部分是动态产生的,而由结点类删除释放数据类对象时,必须由数据类而由结点类删除释放数据类对象时,必须由数据类的析构函数来释放该类对象数据部分占用的动态分的析构函数来释放该类对象数据部分占用的动态分配的内存配的内存。这时必须重新定义析构函数。这时必须重新定义析构函数。Class Node Object* info; /数据域用指针指向数据类对象数据域用指针指向数据类对象 Node* link; /指针域指针域public: Node(); /生成头结点的构造函数生成头结点的构造函数 Node(); /析构函数析构函数 void InsertAfter(Node* P);

44、 /在当前结点后插入一个结点在当前结点后插入一个结点 Node* RemoveAfter(); /删除当前结点的后继结点,返回该结点备用删除当前结点的后继结点,返回该结点备用 void Linkinfo(Object* obj); /把数据对象连接到结点把数据对象连接到结点 friend class List; /以以List为友元类,为友元类,List可直接访问可直接访问Node的私有函数,的私有函数,;【例【例8.108.10】通用单链表派生类】通用单链表派生类结点类定义结点类定义:class List Node *head,*tail; /链表头指针和尾指针链表头指针和尾指针public

45、: List(); /构造函数,生成头结点构造函数,生成头结点(空链表空链表) List(); /析构函数析构函数 void MakeEmpty(); /清空链表,只余表头结点清空链表,只余表头结点 Node* Find(Object & obj); /搜索数据域与定值相同的结点,返回该结点的地址搜索数据域与定值相同的结点,返回该结点的地址 int Length(); /计算单链表长度计算单链表长度 void PrintList(); /打印链表的数据域打印链表的数据域 void InsertFront(Node* p); /可用来向前生成链表可用来向前生成链表 void Insert

46、Rear(Node* p); /可用来向后生成链表可用来向后生成链表 void InsertOrder(Node* p); /按升序生成链表按升序生成链表 Node* CreatNode(); /创建一个结点创建一个结点(孤立结点孤立结点) Node* DeleteNode(Node* p); ; /删除指定结点删除指定结点【例【例8.108.10】通用单链表派生类】通用单链表派生类定义链表类定义链表类第二步第二步,取代模板定义泛型类型为具体类型(包括类),取代模板定义泛型类型为具体类型(包括类)的步骤是由抽象类派生数据类。这里的步骤是由抽象类派生数据类。这里数据采用的是字符数据采用的是字符串

47、串,字符串是放在动态分配的堆内存中的,所以,字符串是放在动态分配的堆内存中的,所以析构函析构函数必须重新定义数必须重新定义。为了完成字符串的比较和输出,重新。为了完成字符串的比较和输出,重新定义了定义了比较和输出函数(虚函数)比较和输出函数(虚函数)。【例【例8.108.10】通用单链表派生类】通用单链表派生类class StringObject:public Object char* sptr;public: StringObject(); /缺省构造函数缺省构造函数 StringObject(char*); /构造函数构造函数 StringObject(); /析构函数析构函数 int C

48、ompare(Object &); /比较函数比较函数 void Print(); /打印函数打印函数;验证主函数验证主函数运行结果运行结果 同学阅读时同学阅读时要特别仔细揣摩堆内存的分配与释放,要特别仔细揣摩堆内存的分配与释放,删删除一个结点时系统除一个结点时系统自动调用自动调用结点类析构函数释放结点占用结点类析构函数释放结点占用的动态内存,而结点释放时系统的动态内存,而结点释放时系统自动调用自动调用数据域类析构函数据域类析构函数释放数据类占用的动态内存数释放数据类占用的动态内存,本例中数据类释放时系统本例中数据类释放时系统自动调用自动调用虚析构函数来实现释放字符串数据的动态内存虚析

49、构函数来实现释放字符串数据的动态内存,一环套一环,一步都不能错。这是使用动态内存分配的关一环套一环,一步都不能错。这是使用动态内存分配的关键。即键。即关键不是创建,而是释放关键不是创建,而是释放! 运行时的多态性需要维护一个动态指针表才能正确指运行时的多态性需要维护一个动态指针表才能正确指向各相关类中的同名虚函数。所以多态与模板比较,模板向各相关类中的同名虚函数。所以多态与模板比较,模板的效率更高,标准模板库中用容器来泛型化数据结构中的的效率更高,标准模板库中用容器来泛型化数据结构中的许多算法。对数据结构的使用当然借助模板库。多态不适许多算法。对数据结构的使用当然借助模板库。多态不适用于性能要

50、求很高的实时应用程序,但继承与多态可用与用于性能要求很高的实时应用程序,但继承与多态可用与其它更多方面,每一种技术都有可以充分发挥自己能力的其它更多方面,每一种技术都有可以充分发挥自己能力的地方。地方。【例【例8.108.10】通用单链表派生类】通用单链表派生类在在VC+平台上运行例平台上运行例8.10。动态联编(动态联编(dynamic binding)亦称滞后联编亦称滞后联编(late binding),对应于),对应于静态联编(静态联编(static binding)。如果使用对象名和点成员选择运算符如果使用对象名和点成员选择运算符“.”引用引用特定的一个对象来调用虚函数,则被调用的虚特

51、定的一个对象来调用虚函数,则被调用的虚函数是在编译时确定的(称为函数是在编译时确定的(称为静态联编静态联编) 如果使用基类指针或引用指明派生类对象并使用该指针如果使用基类指针或引用指明派生类对象并使用该指针调用虚函数(成员选择符用箭头号调用虚函数(成员选择符用箭头号“-”),则程序动态),则程序动态地(运行时)选择该派生类的虚函数,称为地(运行时)选择该派生类的虚函数,称为动态联编动态联编。8.7.3 8.7.3 动态联编动态联编联编是指计算机程序自身彼此关联的过程,是把一个标识符联编是指计算机程序自身彼此关联的过程,是把一个标识符名和一个存储地址联系在一起的过程,也就是把一条消息和名和一个存

52、储地址联系在一起的过程,也就是把一条消息和一个对象的操作相结合的过程一个对象的操作相结合的过程 。图图8.9 8.9 虚函数调用的控制流程虚函数调用的控制流程“dog”“dog”StringObjectStringObject动态无名对象动态无名对象StringObjectStringObject动态无名对象动态无名对象“cat”“cat”指向指向ObjectObject类指针类指针指向结点类指针指向结点类指针指向指向ObjectObject类指针类指针指向结点类指针指向结点类指针指向指向ObjectObject类指针类指针指向结点类指针指向结点类指针StringObjectStringObj

53、ect动态无名对象动态无名对象“cock”“cock” 析构函数指针析构函数指针0 0比较函数指针比较函数指针0 0输出函数指针输出函数指针StringObjectStringObject虚函数表虚函数表抽象类抽象类ObjectObject虚函数表虚函数表析构函数指针析构函数指针比较函数指针比较函数指针输出函数指针输出函数指针ComplexObjectComplexObject虚函数虚函数 析构函数指针析构函数指针 比较函数指针比较函数指针 输出函数指针输出函数指针 缺 省 析缺 省 析构函数构函数释放动态串释放动态串析构函数析构函数串比较函数串比较函数打印串函数打印串函数缺省析构缺省析构函数

54、函数复 数 模 大 小复 数 模 大 小比较函数比较函数打印复数函数打印复数函数8.7.3 8.7.3 动态联编动态联编 C+编译器编译含有一个或几个虚函数的类及其派编译器编译含有一个或几个虚函数的类及其派生类生类 时,对该类建立时,对该类建立虚函数表(虚函数表(Virtual function table,vtable)。 虚函数表使执行程序正确选择每次虚函数表使执行程序正确选择每次执行时应使用的虚函数。执行时应使用的虚函数。 多态是由复杂的数据结构实现的,参见图多态是由复杂的数据结构实现的,参见图8.10。图。图8.10是以【例是以【例8.10】为基础的,不过增加了一个由抽】为基础的,不过

55、增加了一个由抽象类象类Object派生的复数数据类派生的复数数据类ComplexObject。图中图中列出了基类和各派生类的虚函数表,这些表是由指向列出了基类和各派生类的虚函数表,这些表是由指向函数的指针组成的函数的指针组成的。 8.7.3 8.7.3 动态联编动态联编 还有第二层指针,在实例化带虚函数的类(创还有第二层指针,在实例化带虚函数的类(创建对象)时,编译器建对象)时,编译器在对象前加上一个指向该类的在对象前加上一个指向该类的虚函数表的指针虚函数表的指针。 第三层指针是链表结点类对象中第三层指针是链表结点类对象中指向抽象基类指向抽象基类Object的指针的指针(这也可以是引用,但本例

56、是指针)。(这也可以是引用,但本例是指针)。 虚函数的调用是这样进行的,考虑虚函数虚函数的调用是这样进行的,考虑虚函数Compare(),则看含,则看含“cat”的结点。由该结点的的结点。由该结点的info指针找到含指针找到含“cat”的无名对象,再由对象前的的无名对象,再由对象前的指针找到指针找到StringObject虚函数表,移动虚函数表,移动4个字节(一个字节(一个指针占个指针占4个字节)找到比较函数指针,进入串比较个字节)找到比较函数指针,进入串比较函数。函数。8.9 8.9 图书馆流通管理系统设计图书馆流通管理系统设计 继承与多态的应用继承与多态的应用 本节为图书馆流通业务增加杂志

57、借阅功能。本节为图书馆流通业务增加杂志借阅功能。 杂志是图书的一种,对于现刊,是以期为借阅单位,对于杂志是图书的一种,对于现刊,是以期为借阅单位,对于过刊以卷(过刊以卷(Volume)为借阅单位,为简化问题,我们只处理过)为借阅单位,为简化问题,我们只处理过刊借阅,即以卷为借阅单位,每卷分配一个条码。与书籍的不刊借阅,即以卷为借阅单位,每卷分配一个条码。与书籍的不同之处在于,杂志没有单一的作者,只以刊名和卷号借阅。同之处在于,杂志没有单一的作者,只以刊名和卷号借阅。 书籍和杂志图书都有书(杂志)名、条码等共性,都有设书籍和杂志图书都有书(杂志)名、条码等共性,都有设置条码、读取条码及显示等接口

58、,提取二者的共性和公共接口,置条码、读取条码及显示等接口,提取二者的共性和公共接口,定义一个定义一个图书类图书类Book,作为基类,作为基类。 派生出派生出Item书籍和书籍和Magazine杂志两个类。杂志两个类。Item有作者、有作者、分类号等特性,分类号等特性,Magazine有卷号、语言等特性,分别定义相应有卷号、语言等特性,分别定义相应的接口,杂志的语言用一个枚举类型的接口,杂志的语言用一个枚举类型LANG表示。基类表示。基类Book中中还增加一个整型变量还增加一个整型变量Type,目的在于区别派生类是,目的在于区别派生类是Item还是还是Magazine。派生类应对显示虚函数。派生

59、类应对显示虚函数Show()进行重定义显示自进行重定义显示自身的特殊属性。身的特殊属性。 8.9 8.9 图书馆流通管理系统设计图书馆流通管理系统设计继承与多态的应用继承与多态的应用class Book/图书基类图书基类protected:char Title40;/书名书名long Code; /条码条码int Type;/0表示书本,表示书本,1表示杂志表示杂志Book *Next; /为构造读者所借书链表定义的指针为构造读者所借书链表定义的指针public:Book();Book(char *title,long code);void SetCode(long code)Code = c

60、ode;void SetTitle(char* tl)strcpy(Title,tl); void SetType(bool type)Type = type; bool GetType()return Type;long GetCode()return Code;virtual void Show(); ; /显示函数定义为虚函数显示函数定义为虚函数8.9 8.9 图书馆流通管理系统设计图书馆流通管理系统设计 继承与多态的应用继承与多态的应用class Item :public Book /书籍类书籍类 char Author20;/作者作者 char IndexCode10;/分类号分类号public: Item();/缺省

温馨提示

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

最新文档

评论

0/150

提交评论