第8章 继承和派生 8.1 继承与派生 8.2 派生类对基类成员的访问 8.3 派生类的构造函数与析构函数 8.4 多重继承与虚基类.

Slides:



Advertisements
Similar presentations
上机考核标准 1.程序基本结构和功能60% 2.程序安全性(逻辑错误、内存溢出和测试错误等)20%
Advertisements

课程 要求 参考 书目 课程 内容 课程 练习.
面向对象的C++程序设计基础 第 4 章 继承与派生.
第6章 多态性与虚函数.
Oracle数据库 Oracle 子程序.
7.2 访问控制 —— 公有继承 公有继承练习 //Point.h #ifndef _POINT_H #define _POINT_H class Point { //基类Point类的定义 public: //公有函数成员 void initPoint(float x = 0, float.
内容提要 代码重用 类的继承 多态 抽象类 多重继承 虚拟继承. 常宝宝 北京大学计算机科学与技术系
计算机可视化编程 基于Visual C++6.0的面向对象编程 第 四 讲 主讲教师:隋振                学时:32.
C++面向对象程序设计 第八章 继承和派生.
第7单元 面向过程编程—— 继承与多态.
第12章 组合与继承 欢迎辞 第14次见面!.
第6章 继 承 主讲人:陈南京.
第四章 继承和派生类 汽车 专用汽车 运输汽车 货车 客车 消防车 洒水车 最普遍、最一般,可以自行驱动 含有汽车的特性,同时与汽车有不同
第14章 c++中的代码重用.
C++语言程序设计 第七章 继承与派生 清华大学 郑 莉.
C++ 面对对象程序设计 授课老师:.
第11章 类的继承和派生 继承是面向对象程序设计方法的四个基本特征之一,是程序代码可重用性的具体体现。
C++中的声音处理 在传统Turbo C环境中,如果想用C语言控制电脑发声,可以用Sound函数。在VC6.6环境中如果想控制电脑发声则采用Beep函数。原型为: Beep(频率,持续时间) , 单位毫秒 暂停程序执行使用Sleep函数 Sleep(持续时间), 单位毫秒 引用这两个函数时,必须包含头文件
在PHP和MYSQL中实现完美的中文显示
第10讲 Java面向对象编程基础(4) 教学目标 主要内容.
上机考核标准 1.程序基本结构和功能60% 2.程序安全性(逻辑错误、内存溢出和测试错误等)20%
4.1 概述 4.2 类与对象的实现 4.3 对象的初始化和析构 4.4 类的包含 4.5 类模板
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
授课老师:龚涛 信息科学与技术学院 2018年3月 教材: 《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
授课老师:龚涛 信息科学与技术学院 2018年4月 教材:《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
第11讲 类的继承 1. 类的继承的概念 2. 类的单继承机制 3. 单继承中的构造函数和析构函数.
第12讲 多继承与虚基类 多继承 虚基类.
走进编程 程序的顺序结构(二).
辅导课程六.
第9章 类和对象(一) 9.1 面向对象的基本概念 9.2 类与对象的声明和定义 9.3 成员函数 9.4 对象的访问 9.5 对象的存储.
第1章 C++基础.
第一单元 初识C程序与C程序开发平台搭建 ---观其大略
面向对象程序设计 QQ群: Object-Oriented Programming 汽车学院.
第9讲 Java的继承与多态(一) 类的继承 子类的创建 方法覆盖.
第八章 继承与派生 丘志杰 电子科技大学 计算机学院 软件学院.
动态规划(Dynamic Programming)
第十一章 继承和派生. 主讲教师:全红艳 第十一章 继承和派生.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
第七章 操作符重载 胡昊 南京大学计算机系软件所.
C++大学基础教程 第9章 继承与派生 北京科技大学.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
C语言程序设计 主讲教师:陆幼利.
简单介绍 用C++实现简单的模板数据结构 ArrayList(数组, 类似std::vector)
第11讲 类的继承 1. 类的继承的概念 2. 类的单继承机制 3. 单继承中的构造函数和析构函数.
第12讲 多继承与虚基类 多继承 虚基类.
$9 泛型基础.
C++复习3 ----类的继承与派生.
C#面向对象程序设计 $6 深入理解类.
第13讲 多态 友员函数 多态性与虚函数 纯虚函数和抽象类.
授课老师:龚涛 信息科学与技术学院 2016年3月 教材:《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
本节内容 类成员的访问控制 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
本节内容 结构体 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
多层循环 Private Sub Command1_Click() Dim i As Integer, j As Integer
C++语言程序设计 C++语言程序设计 第八章 继承 C++语言程序设计.
C++语言程序设计教程 第8章 继承与派生 第8章 继承与派生 制作人:杨进才.
辅导课程十五.
第7章 模板 陈哲 副教授 南京航空航天大学 计算机科学与技术学院.
本节内容 C语言的汇编表示 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
C++语言程序设计 C++语言程序设计 第十一章 异常处理 C++语言程序设计.
C++与数据结构简明教程 第五章 类和对象.
C++语言程序设计 C++语言程序设计 第八章 继承 C++语言程序设计.
基于列存储的RDF数据管理 朱敏
本节内容 动态链接库 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第十章 多态 第十一组 C++语言程序设计.
第十二讲 继承 与 派生.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
本节内容 this指针 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
Presentation transcript:

第8章 继承和派生 8.1 继承与派生 8.2 派生类对基类成员的访问 8.3 派生类的构造函数与析构函数 8.4 多重继承与虚基类

第8章 继承和派生 教学目标: 教学重点: 教学难点: 1. 掌握C++语言中类的继承机制、语法规则及子类对基类成员的覆盖; 第8章 继承和派生 教学目标: 1. 掌握C++语言中类的继承机制、语法规则及子类对基类成员的覆盖; 2 .了解单继承、多重继承等有关概念及其差别。 教学重点: 1. 派生类对基类的继承本质; 2. 派生类构造函数和析构函数工作机制 ; 3. 虚基类的应用; 教学难点: 1.虚基类的应用。

8.1 继承与派生 8.1.1 基本概念 8.1.2 派生的定义与构成

8.1.1 基本概念 继承:面向对象程序设计中,可以在已有类的基础上定义新的类,而不需要把已有类的内容重新书写一遍,这就叫做继承。 8.1.1 基本概念 继承:面向对象程序设计中,可以在已有类的基础上定义新的类,而不需要把已有类的内容重新书写一遍,这就叫做继承。 基类: 已有类称为基类或父类, 派生类: 在已有类的基础上建立的新类称为派生类或导出类、子类。 基类和派生类是继承中的两个概念,被继承的类称为基类,继承的类称为派生类。

单继承: 如果一个对象从单个基类中继承了属性,就被称为单继承; 多重继承: 如果一个对象从多个基类中继承了属性,就被称为多重继承。 继承性允许一个类从其他类中继承属性, 从而实现代码重用。

图8.1 多重继承与单继承 一个基类可以直接派生出多个派生类 (b)单继承 (a)多重继承 派生类可以由多个基类共同派生出来,称多重继承。 基类1 基类2 …… 基类n 派生类1 派生类2 基类 派生类1 派生类2 派生类可以由多个基类共同派生出来,称多重继承。 (b)单继承 (a)多重继承 图8.1 多重继承与单继承

多层次继承: 在派生过程中,派生出来的新类同样可以作为基类再继续派生出更新的类,依此类推形成一个层次结构。直接参与派生出某类称为直接基类,而基类的基类,以及更深层的基类称为间接基类。 类族: 同时一个基类可以直接派生出多个派生类。这样形成了一个相互关联的类族。如MFC就是这样的族类,它由一个CObject类派生出200个MFC类中的绝大多数。

8.1.2 派生类的定义与构成 派生类的定义: 一个派生类可以看作是对现有类的继承或扩展,派生类提供了扩展或定制基类特性的简单手段,不需要重新来创建基类本身。 实现继承的方法是类的派生,任何一个类都可以作为基类,从这个基类可以派生出多个类. 派生的类不仅具有基类的特征,而且还可以定义自己独有的特征。程序员可以通过类的派生来构造可重用的类库。

派生类的定义格式: class 派生类名:继承方式 基类名{ private: 成员表1; //派生类增加或替代的私有成员 public: 成员表2; //派生类增加或替代的公有成员 protected: 成员表3; //派生类增加或替代的保护成员 };//分号不可少 其中基类是已声明的类。 在派生类定义的类体中给出的成员称为派生类成员,它们是新增加成员,它们给派生类添加了不同于基类的新的属性和功能。派生类成员也包括取代基类成员的更新成员。 继承方式决定了子类对父类的访问权限,有3种继承方式: private、public和protected,默认为private,最常用的是public。

关于继承的几点说明: ① 派生类自动具有基类的全部数据成员和成员函数;但是,派生类对基类成员的访问有所限制。 ② 派生类可以定义自己的成员: 数据成员和成员函数。 ③ 基类、派生类或父类、子类都是“相对”的。一个类派生出新的类就是基类。派生类也可以被其他类继承,这个派生类同时也是基类。 构造函数和析构函数是不能继承的,对派生类要重新定义构造函数和析构函数.

派生编程步骤: 吸收基类的成员 编制派生类时可分四步 改造基类成员 发展新成员 重写构造函数与析构函数 不论是数据成员,还是函数成员,除构造函数与析构函数外全盘接收 编制派生类时可分四步 改造基类成员 声明一个和某基类成员同名的新成员,派生类中的新成员就屏蔽了基类同名成员称为同名覆盖(override) 发展新成员 派生类新成员必须与基类成员不同名,它的加入保证派生类在功能上有所发展。 重写构造函数与析构函数

第二步中,新成员如是成员函数,参数表也必须一样,否则是重载。 第三步中,独有的新成员才是继承与派生的核心特征。 第四步 是重写构造函数与析构函数,派生类不继承这两种函数。不管原来的函数是否可用,一律重写可免出错。

class C: public B { //可扩展到任意级数 例如: 基类可写为: class A {    //类成员列表   }; class B: public A { // //增加或替代的成员列表 class C: public B { //可扩展到任意级数

8.2 派生类对基类成员的访问 继承方式:亦称为访问控制,是对基类成员进一步的限制。访问控制也是三种: 公有(public)方式,亦称公有继承 保护(protected)方式,亦称保护继承 私有(private)方式, 亦称私有继承。 访问限定符两方面含义:  (1) 派生类成员(新增成员)函数对基类(继承来的)成员的访问(调用和操作), (2)从派生类对象之外对派生类对象中的基类成员的访问。

(1) 公有继承: 派生时用public作继承方式。 基类的私有(private)成员在派生类中不可见。 基类的保护(protected)成员被继承为保护的。

(2) 私有继承: 派生时用private作继承方式。 基类的公有段(public)成员被继承为私有的。 基类的私有段(private)成员在派生类中不可见。 基类的保护段(protected)成员被继承为私有的。

(3) 保护继承: 派生时用protected作继承方式。 基类的公有段(public)成员被继承为保护的。 基类的私有段(private)成员在派生类中不可见。 基类的保护段(protected)成员被继承为保护的。

公有派生是绝对主流。 不可直接访问 private protected public 私有派生 可直接访问 公有派生 在派生类对象外访问派生类对象的基类成员 在派生类中对基类成员的访问限定 基类中的访问限定 派生方式 公有派生是绝对主流。

class A { //基类 private: int priA; protected: int proA; public: int pubA; };

class B : public A { //派生类 void fn() { int a; a = priA; //错误:不可访问 a = proA; //有效 a = pubA; //有效 } };

void main() { A a; //基类对象 a.priA = 1; //错误:不可访问 a.proA = 1; //错误:不可访问 a.pubA = 1; //有效 B b; //派生类对象 b.priA = 1; //错误:不可访问 b.proA = 1; //错误:不可访问 b.pubA = 1; //有效 }

8.3 派生类的构造函数与析构函数 8.3.1 派生类的构造函数 8.3.2 派生类的析构函数

8.3.1 派生类的构造函数 派生类构造函数: 在派生关系中,构造函数和析构函数是不能继承的,对派生类要重新定义构造函数和析构函数。 派生类构造函数:   在派生关系中,构造函数和析构函数是不能继承的,对派生类要重新定义构造函数和析构函数。 (1)一个派生类对象中也包含了基类数据成员的值,所以在生成一个派生类对象时,系统首先要通过派生类的构造函数调用基类中的构造函数,对基类成员初始化,然后对派生类中新增的成员初始化。 (2)也就是说,派生类的构造函数除了对新增成员进行初始化之外,还要调用基类的构造函数,对基类进行初始化。

派生类构造函数的定义: 派生类名::派生类名(参数总表):基类名1(参数名表1)《,基类名2(参数名表2),……,基类名n(参数名表n)》,《成员对象名1(成员对象参数名表1),……,成员对象名m(成员对象参数名表m)》{ ……//派生类新增成员的初始化; } //所列出的成员对象名全部为新增成员对象的名字 注意: 在构造函数的声明中,冒号及冒号以后部分必须略去。 所谓不能继承并不是不能利用,而是把基类的构造函数作为新的构造函数的一部分,或者讲调用基类的构造函数。基类名仅指直接基类,写了底层基类,编译器认为出错。 冒号后的基类名,成员对象名的次序可以随意,这里的次序与调用次序无关。

派生类构造函数各部分执行次序: 注意: 1.调用基类构造函数,按它们在派生类定义的先后顺序,顺序调用。 2.调用成员对象的构造函数,按它们在类定义中声明的先后顺序,顺序调用。 3.派生类的构造函数体中的操作。 注意: 在派生类构造函数中,只要基类不是使用无参的默认构造函数,都要显式给出基类名和参数表。 如果基类没有定义构造函数,则派生类也可以不定义,全部采用系统给定的默认构造函数。 如果基类定义了带有形参表的构造函数,派生类就应当定义构造函数。

8.3.2 派生类的析构函数 析构函数: 析构函数的功能是作善后工作。 8.3.2 派生类的析构函数 析构函数: 析构函数的功能是作善后工作。 只要在函数体内把派生类新增一般成员处理好就可以了,而对新增的成员对象和基类的善后工作,系统会自己调用成员对象和基类的析构函数来完成。 析构函数各部分执行次序与构造函数相反,首先对派生类新增一般成员析构,然后对新增成员对象析构,最后对基类成员析构。

class Base1 { int x; public: Base1(int a){ x=a;cout<<"调用基类1的构造函数!\n"; } ~Base1( ){ cout<<"调用基类1的析构函数!\n"; } }; class Base2 { int y; public: Base2(int a){ y=a;cout<<"调用基类2的构造函数!\n"; } ~Base2( ){ cout<<"调用基类2的析构函数!\n"; } class Derived:public Base2, public Base1{ int z; Base1 b1,b2; public: Derived(int a,int b):Base1(a),Base2(20), b1(200),b2(a+b) {z=b; cout<<"调用派生类的构造函数!\n";} ~Derived( ){cout<<"调用派生类的析构函数!\n";} void main(void) { Derived c(100,200); }

调用基类2的构造函数 说明基类1的对象b1,b2 调用基类1的构造函数 调用基类1的构造函数 调用基类1的构造函数 调用派生类的构造函数 调用派生类的析构函数 调用基类1的析构函数 调用基类1的析构函数 调用基类1的析构函数 调用基类2的析构函数

【例8.1】由在册人员类公有派生学生类 【例8.1】由在册人员类公有派生学生类。我们希望基类和派生类共享相同的公有接口,只能采用公有派生来实现。 基类: class Person{ string IdPerson; //身份证号,18位数字 string Name; //姓名 Tsex Sex; //性别enum Tsex{mid,man,woman}; int Birthday; //生日,格式1986年8月18日写作19860818 string HomeAddress; //家庭地址 public: Person(string, string,Tsex,int, string);//构造函数 Person(); //默认的构造函数 ~Person(); //析构函数

//接口函数: string GetName(){return Name;} //提取名字 void SetName(string); //修改名字 string GetName(){return Name;} //提取名字 void SetSex(Tsex sex){Sex=sex;} //修改性别 Tsex GetSex(){return Sex;} //提取性别 void SetId(string id){IdPerson=id;}//修改身份证号 string GetId(){return IdPerson;} //提取身份证号 void SetBirth(int birthday){Birthday=birthday;} //修改生日 int GetBirth(){return Birthday;} //提取生日 void SetHomeAdd(string ); //修改住址 string GetHomeAdd(){return HomeAddress;} //提取住址 void PrintPersonInfo(); //输出个人信息 };

派生的学生类: class Student:public Person{ //定义派生的学生类 string NoStudent; //学号 course cs[30]; //30门课程与成绩 public: Student(string id, string name,Tsex sex,int birthday, string homeadd, string nostud); //注意派生类构造函数声明方式 Student(); //默认派生类构造函数 ~Student(); //派生类析构函数 SetCourse(string ,int); //课程设置 int GetCourse(string ); //查找成绩 void PrintStudentInfo(); //打印学生情况 }; struct course{ string coursename; int grade;}; 验证主函数

单继承中的构造函数和析构函数的应用说明: ① 派生类的构造函数的初始化列表中列出的均是直接基类的构造函数。 ② 构造函数不能被继承,因此派生类的构造函数只能通过调用基类的某个构造函数(如果有重载的话)来初始化基类子对象。 ③ 派生类的构造函数只负责初始化自己定义的数据成员。 ④ 先调用基类的构造函数,再调用派生类自己的数据成员所属类的构造函数,最后调用派生类的构造函数;派生类的数据成员的构造函数被调用的顺序取决于在类中声明的顺序。

单继承中的构造函数和析构函数的应用说明: ⑤ 析构函数不可以继承,不可以被重载,也不需要被调用。 ⑥ 派生类的对象的生存期结束时自动调用派生类的析构函数,在该析构函数结束之前再自动调用基类的析构函数;所以,析构函数的被自动调用次序与构造函数相反。

8.4 多重继承与虚基类 8.4.1 多重继承的二义性问题 8.4.2 虚基类的定义 8.4.3 虚基类的构造函数

8.4.1 多重继承的二义性问题 多重继承: 由多个基类共同派生出新的派生类,这样的继承结构被称为多重继承或多继承. 椅子 床 8.4.1 多重继承的二义性问题 多重继承: 由多个基类共同派生出新的派生类,这样的继承结构被称为多重继承或多继承. 椅子 床 沙发(单继承) 躺椅(多重继承) 两用沙发(多重继承) 图8.2 椅子,床到两用沙发

派生出来的新类同样可以作为基类再继续派生出更新的类,依此类推形成一个层次结构。 在册人员 学生(单继承) 教职工(单继承) 兼职教师(单继承) 教师(单继承) 行政人员(单继承) 工人(单继承) 研究生(单继承) 行政人员兼教师 (多重继承) 在职研究生 研究生助教 派生出来的新类同样可以作为基类再继续派生出更新的类,依此类推形成一个层次结构。 图8.3 大学在册人员继承关系

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

定义EGStudent类对象EGStud1,并假定派生全部为公有派生,而int No全为公有成员: class Person int No 身份证号 ………. class Person int No 身份证号 ………. 定义EGStudent类对象EGStud1,并假定派生全部为公有派生,而int No全为公有成员: EGStud1.No //在职学号 EGStud1.GStudent::No //研究生号 EGStud1.GStudent.Student::No //学生号 EGStud1.GStudent.Student. Person::No //身份证号 EGStud1.Employee::No //工作证号 EGStud1.Employee.Person::No //身份证号 class Employee int No 工作证号 ………. class Student int No 学生号 ………. class GStudent int No 研究生号 ………. 两个身份证号从逻辑上讲应是一回事,但是物理上是分配了不同内存空间,是两个变量,请参见图7.4(b)。 class EGStudent int No 在职学号 ……… 图8.4(a)在职研究生派生类关系

EGStud1.Employee::IdPerson 不必标出那么多层次的类,但写EGStud1.IdPerson是错的。 Student Employee GStudent EGStudent Person成员 Student新成员 GStudent新成员 Employee新成员 EGStudent新成员 图7.4(b)在职研究生派生类存储图 建议采用有确定字面意思的标识符,它可以被编译器简单区分出来。 如果class Person的身份证号标识为int IdPerson,则写为:EGStud1.GStudent::IdPerson EGStud1.Employee::IdPerson 不必标出那么多层次的类,但写EGStud1.IdPerson是错的。 作用域分辨符不能嵌套使用,如: EGStud1.GStudent::Student::No //学生号 EGStud1.GStudent::Student::Person::No //身份证号 是错误的。

注意: 一般数据成员总是私有成员,派生类对基类的访问只能间接进行。访问身份证号,应通过class Person中的公有成员函数(接口)GetNo()和SetNo()进行: EGStud1.Employee.Person::SetNo(int no); no=EGStud1.Employee.Person::GetNo();

【例8.2】由圆和高多重继承派生出圆锥 【例8.2】由圆和高多重继承派生出圆锥。 圆类Circle定义 高类Line定义 圆锥类Cone定义 因为公有派生时,在派生类中不可以直接访问基类的私有成员,但可以直接访问基类的保护成员,当需要在派生类中访问基类的数据成员时,可以将它们定义为保护的,而不是私有的。 本例中类Circle为圆;类Line为高;类Cone为圆锥,由Circle和Line公有派生而来。在Cone类中,Circle和Line类的接口完全不变,可以直接调用,这就是公有派生的优点。在Cone的成员函数中可直接访问Circle和Line中的公有成员和保护成员。   圆类Circle定义 高类Line定义 圆锥类Cone定义 检证主程序:

8.4.2 虚基类的定义 虚基类的引入: 在多继承中,若在多条继承路径上有公共基类,这个公共基类便会产生多个副本。为了解决这个问题,把公共基类定义为虚基类。使用虚基类的继承称为虚拟继承。 虚基类是对派生类而言,虚基类本身的定义同基类一样,在定义派生类时声明该基类为虚基类即可,就是冠以关键字virtual。 例如, 在图7.4中,两个身份证号显然是不合理的。可以把class Person这个共同基类设置为虚基类,这样就仅有一个Person基类成员,从不同路径继承来的同名数据成员(身份证号)在内存中就是同一个数据,从而解决了多重继承的二义性问题。

虚基类定义: class 派生类名:virtual 继承方式 基类类名{...}; class 派生类名: 继承方式 virtual 基类类名{...}; 注意: virtual 关键字只对紧随其后的基类名起作用: class Student:virtual public Person{...}; class Employee:virtual public Person{...};

虚拟继承: 在Person的位置上放的是指针,两个指针都指向Person成员存储的内存。这种使用虚基类的继承称为虚拟继承, 虚拟继承 图8.5 采用虚基类后在职研究生类储存图 Student GStudent EGStudent Person Student新成员 GStudent新成员 Employee新成员 Person成员 EGStudent新成员 Employee 虚拟继承 在Person的位置上放的是指针,两个指针都指向Person成员存储的内存。这种使用虚基类的继承称为虚拟继承,

8.4.3 虚基类的构造函数 虚拟继承的构造函数: 派生类名::派生类名(参数总表):基类名1(参数名表1)《,基类名2(参数名表2),……,基类名n(参数名表n)》,《成员对象名1(成员对象参数名表1),……,成员对象名m(成员对象参数名表m)》,底层虚基类名1(参数名表1)《,……, 底层虚基类名r(参数名表r)》{ ……//派生类新增成员的初始化 }; //所列出的成员对象名全部为新增成员对象的名字 在多层虚拟继承构造函数中,基类名不仅要列出直接基类,而且要列出底层虚基类,否则编译器认为出错。

构造函数执行次序: 在派生类对象的创建中: (1) 首先是虚基类的构造函数并按它们声明的顺序构造。 (2) 第二批是非虚基类的构造函数按它们声明的顺序调用。 (3) 第三批是成员对象的构造函数。 (4) 最后是派生类自己的构造函数被调用。

【例8.3】在采用虚基类的多重继承中,构造与析构的次序。 class Dclass:public Bclass1,virtual Bclass3, virtual Bclass2{ Object object; public: Dclass(): object() ,Bclass2(),Bclass3(),Bclass1(){ cout<<"派生类建立!\n";} ~Dclass(){cout<<"派生类析构!\n";} }; int main(){ Dclass dd; cout<<“主程序运行!\n”;return 0; }

运行结果: Constructor Bclass3 //第一个虚拟基类,与派生类析构函数排列无关 Constructor Object //对象成员 派生类建立! 主程序运行! 派生类析构! deconstructor Object //析构次序相反 deconstructor Bclass1 deconstructor Bclass2 deconstructor Bclass3 //析构的次序与构造的次序相反。

【例8.4】虚基类在多层多重继承中的应用 ——在职研究生类定义。 以虚基类定义公有派生的学生类 以虚基类定义公有派生的研究生类 以虚基类定义公有派生的教职工类 多重继承的以虚基类定义公有派生的在职研究生类 对照图8.5,尽管Employee和Student的构造函数都包含Person的构造函数,但并未真正调用。唯一的一次调用是在EGStudent构造函数中。如是非虚基类,则有两次调用。

第8章 继承和派生 【例8.1】由在册人员类公有派生学生类 分析构造函数: 第8章 继承和派生 【例8.1】由在册人员类公有派生学生类 分析构造函数: Person::Person(string id, string name,Tsex sex,int birthday, string homeadd){ IdPerson=id; Name=name; Sex=sex; Birthday=birthday; HomeAddress=homeadd; } //作为一个管理程序,这个构造函数并无必要,因为数据总是另外输入的。仅为说明语法存在。

//string内部动态数组的释放,由string自带的析构函数完成 分析默认的构造函数: Person::Person(){ IdPerson="#";Name="#";Sex=mid; Birthday=0;HomeAddress="#"; } 分析析构函数: Person::~Person(){} //string内部动态数组的释放,由string自带的析构函数完成

void Person::SetName(string name){ Name=name; //拷入新姓名 } 修改名字: void Person::SetName(string name){ Name=name; //拷入新姓名 } 修改住址: void Person::SetHomeAdd(string homeadd){ HomeAddress=homeadd; }

void Person::PrintPersonInfo(){ int i; 输出个人信息: void Person::PrintPersonInfo(){ int i; cout<<"身份证号:"<<IdPerson<<'\n'<<"姓名:" <<Name<<'\n'<<"性别:"; if(Sex==man)cout<<"男"<<'\n'; else if(Sex==woman)cout<<"女"<<'\n'; else cout<<" "<<'\n'; cout<<"出生年月日:"; i=Birthday; cout<<i/10000<<"年"; i=i%10000; cout<<i/100<<"月"<<i%100<<"日"<<'\n‘ <<"家庭住址:"<<HomeAddress<<'\n'; }

派生类构造函数: Student::Student (string id, string name,Tsex sex,int birthday, string homeadd, string nostud) :Person(id,name,sex,birthday,homeadd){ //注意Person参数表不用类型 int i; NoStudent=nostud; for(i=0;i<30;i++){ //课程与成绩清空 cs[i].coursename="#"; cs[i].grade=0; }

默认派生类构造函数: Student::Student(){//基类默认的无参数构造函数不必显式给出 int i; NoStudent=""; for(i=0;i<30;i++){ //课程与成绩清零,将来由键盘输入 cs[i].coursename=""; cs[i].grade=0; } 派生类析构函数: Student::~Student(){} //基类析构函数以及成员对象析构函数自动调用

学生类课程设置函数: int Student::SetCourse(string coursename,int grade){ bool b=false; //标识新输入的课程,还是更新成绩 int i; for(i=0;i<30;i++){ if(cs[i].coursename=="#"){ //判表是否进入未使用部分(如有对应删除,应按顺序表方式) cs[i].coursename=coursename; cs[i].grade=grade; b=false;break; } else if(cs[i].coursename==coursename){ //是否已有该课程记录 cs[i].grade=grade;b=true;break;}} if(i==30) return 0; //成绩表满返回0 if(b) return 1; //修改成绩返回1 else return 2; //登记成绩返回2 }

查找学生课程成绩函数: int Student::GetCourse(string coursename){ int i; for(i=0;i<30;i++) if(cs[i].coursename==coursename) return cs[i].grade; return -1; } //找到返回成绩,未找到返回-1

打印学生情况函数: void Student::PrintStudentInfo(){ int i; cout<<"学号:"<<NoStudent<<'\n'; PrintPersonInfo(); for(i=0;i<30;i++) //打印各科成绩 if(cs[i].coursename!="#") cout<<cs[i].coursename <<'\t'<<cs[i].grade<<'\n'; else break; cout<<"--------完-------- "<<endl; }

例8.1验证用主函数: int main(void){ char temp[30]; int i,k; Person per1("320102820818161","沈俊", man,19820818,"南京四牌楼2号"); Person per2; per2.SetName("朱明"); per2.SetSex(woman); per2.SetBirth(19780528); per2.SetId("320102780528162"); per2.SetHomeAdd("南京市成贤街9号"); per1.PrintPersonInfo(); per2.PrintPersonInfo(); Student stu1("320102811226161","朱海鹏", man,19811226,"南京市黄浦路1号","06000123");

cout<<"请输入各科成绩:"<<'\n'; //完整的程序应输入学号,查找,再操作 while(1){ //输入各科成绩,输入"end"停止 cin>>temp; //输入格式:物理 80 if(!strcmp(temp,"end")) break; cin>>k; i=stu1.SetCourse(temp,k); if(i==0)cout<<"成绩列表已满!"<<'\n'; else if(i==1)cout<<"修改成绩"<<'\n'; else cout<<"登记成绩"<<'\n'; } stu1.PrintStudentInfo(); while(1){ cout<<"查询成绩"<<'\n'<<"请输入科目:"<<'\n'; cin>>temp; k=stu1.GetCourse(temp); if(k==-1)cout<<"未查到"<<'\n'; else cout<<k<<'\n'; } return 0;}

Circle(float a=0,float b=0,float R=0){x=a;y=b;r=R;} 【例8.2】由圆和高多重继承派生出圆锥 class Circle{ protected: float x,y,r; //(x,y)为圆心,r为半径 public: Circle(float a=0,float b=0,float R=0){x=a;y=b;r=R;} void Setcoordinate(float a,float b){x=a;y=b;} //设置圆心坐标 void Getcoordinate(float &a,float &b){a=x;b=y;} void SetR(float R){r=R;} //设置半径 float GetR(){return r;} //取圆半径 float GetAreaCircle(){return float(r*r*3.14159);} //取圆面积 float GetCircumference(){return float(2*r*3.14159);} //取圆周长};

高类Line: class Line{ protected: float High; public: Line(float a=0){High=a;} void SetHigh(float a){High=a;} float GetHigh(){return High;} };

派生类圆锥: class Cone:public Circle,public Line{ public: Cone(float a, float b, float R, float d ):Circle( a,b,R ),Line( d ) { } float GetCV(){ return float( GetAreaCircle()*High/3); } //取得圆锥体积 float GetCA(){ //取得圆锥表面积 return float( GetAreaCircle()+r*3.14159*sqrt(r*r+High*High)); }; //共有派生类中能直接访问直接基类的保护成员

检证主程序: void main(){ Cone c1(5,8,3,4); float a,b; cout<<"圆锥体积:"<<c1.GetCV()<<'\n'; cout<<"圆锥表面积:"<<c1.GetCA()<<'\n'; cout<<"圆锥底面积:"<<c1.GetAreaCircle()<<'\n'; cout<<"圆锥底周长:"<<c1.GetCircumference()<<'\n'; cout<<"圆锥底半径:"<<c1.GetR()<<'\n'; c1.Getcoordinate(a,b); cout<<"圆锥底圆心坐标:("<<a<<','<<b<<")\n"; cout<<"圆锥高:"<<c1.GetHigh()<<'\n'; }

[例8.4] 虚基类与在职研究生 以虚基类定义公有派生的学生类 [例8.4] 虚基类与在职研究生 以虚基类定义公有派生的学生类 class Student:public virtual Person{ string NoStudent; //学号 //30门课程与成绩略 public: Student(string id, string name,Tsex sex,int birthday, string homeadd, string nostud); Student(); ~Student(){cout<<"析构Student"<<endl;} void PrintStudentInfo(); }; Student::Student(string id, string name,Tsex sex, int birthday, string homeadd, string nostud) :Person(id,name,sex,birthday,homeadd){ //注意Person参数名表不用类型 cout<<"构造Student"<<endl; NoStudent=nostud;}

以虚基类定义公有派生的研究生类 class GStudent:public Student{ //以虚基类定义公有派生的研究生类 string NoGStudent; //研究生号,其他略 public: GStudent(string id, string name,Tsex sex,int birthday, string homeadd, string nostud,string nogstudent); //注意派生类构造函数声明方式 GStudent(); ~GStudent(){cout<<"析构GStudent"<<endl;}; void PrintGStudentInfo();}; GStudent::GStudent(string id, string name,Tsex sex, int birthday, string homeadd, string nostud, string nogstud): Student(id,name,sex,birthday,homeadd,nostud), Person(id,name,sex,birthday,homeadd){ //因Person是虚基类,尽管不是直接基类, Person必须出现。 //不定义对象可不出现,为通用应出现。如不是虚基类,出现是错误的 cout<<"构造GStudent"<<endl; NoGStudent=nogstud;}

以虚基类定义公有派生的教职工类 class Employee:public virtual Person{ string NoEmployee; //教职工号,其他略 public: Employee(string id, string name,Tsex sex,int birthday, string homeadd, string noempl); Employee(); ~Employee(){cout<<"析构Employee"<<endl;} void PrintEmployeeInfo(); void PrintEmployeeInfo1();//多重继承时避免重复打印虚基类Person的信息 }; Employee::Employee(string id, string name,Tsex sex,int birthday, string homeadd, string noempl) :Person(id,name,sex,birthday,homeadd){ cout<<"构造Employee"<<endl; NoEmployee=noempl; }

多重继承的以虚基类定义公有派生的在职研究生类 class EGStudent:public Employee,public GStudent{ string NoEGStudent; //在职学习号,其他略 public: EGStudent(string id, string name,Tsex sex,int birthday,string homeadd, string nostud,string nogstud, string noempl, string noegstud); EGStudent(); ~EGStudent(){cout<<"析构EGStudent"<<endl;}; void PrintEGStudentInfo();}; EGStudent::EGStudent(string id, string name,Tsex sex,int birthday, string homeadd,string nostud, string nogstud, string noempl, string noegstud) :GStudent(id,name,sex,birthday,homeadd,nostud,nogstud), Employee(id,name,sex,birthday,homeadd,noempl), Person(id,name,sex,birthday,homeadd){ cout<<"构造EGStudent"<<endl; NoEGStudent=noegstud;}

小结: 思考题: 作业题: 掌握C++语言中类的继承机制、语法规则及子类对基类成员的覆盖;了解单继承、多重继承等有关概念及其差别。 1.什么是类的继承和派生? 2.列举类的三中继承方式? 作业题: 1.在访问对象成员的过程中如何解决成员的唯一标识问题?