第 8 章 继承和派生

68
第8第 第第第第第 8.1 继继继继继 8.2 继继继继继继继继继继继 8.3 继继继继继继继继继继继继继 8.4 继继继继继继继继

description

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

Transcript of 第 8 章 继承和派生

Page 1: 第 8 章   继承和派生

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

Page 2: 第 8 章   继承和派生

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

Page 3: 第 8 章   继承和派生

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

Page 4: 第 8 章   继承和派生

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

8.1.1 8.1.1 基本概念基本概念

Page 5: 第 8 章   继承和派生

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

Page 6: 第 8 章   继承和派生

基 类1

基 类2

…… 基 类n

派生类 1 派生类 2

基类

派生类 1 派生类 2

( a )多重继承 ( b )单继承 图 8.1 多重继承与单继承

一个基类可以直接派生出多个派生类 派生类可以由多个基类共同派生出来,称多重继承。

Page 7: 第 8 章   继承和派生

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

多层次继承:

Page 8: 第 8 章   继承和派生

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

8.1.2 8.1.2 派生类的定义与构成派生类的定义与构成

Page 9: 第 8 章   继承和派生

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

Page 10: 第 8 章   继承和派生

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

Page 11: 第 8 章   继承和派生

编制派生类时可分四步

吸收基类的成员

改造基类成员

发展新成员

重写构造函数与析构函数

不论是数据成员,还是函数成员,除构造函数与析构函数外全盘接收

声明一个和某基类成员同名的新成员 , 派生类中的新成员就屏蔽了基类同名成员称为同名覆盖( override ) 派生类新成员必须与基类成员不同名,它的加入保证派生类在功能上有所发展。

派生编程步骤:

Page 12: 第 8 章   继承和派生

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

Page 13: 第 8 章   继承和派生

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

Page 14: 第 8 章   继承和派生

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

Page 15: 第 8 章   继承和派生

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

Page 16: 第 8 章   继承和派生

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

Page 17: 第 8 章   继承和派生

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

Page 18: 第 8 章   继承和派生

不可直接访问 不可直接访问 private 不可直接访问 private protected 不可直接访问 private public 私有派生 不可直接访问 不可直接访问 private 不可直接访问 protected protected 可直接访问 public public 公有派生

在派生类对象外访问派生类对象的基类成员 在派生类中对基类成员的访问限定

基类中的访问限定 派生方式

公有派生是绝对主流。

Page 19: 第 8 章   继承和派生

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

Page 20: 第 8 章   继承和派生

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

Page 21: 第 8 章   继承和派生

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

Page 22: 第 8 章   继承和派生

8.3 8.3 派生类的构造函数与析构函派生类的构造函数与析构函数数• 8. 3 .1 派生类的构造函数• 8. 3 .2 派生类的析构函数

Page 23: 第 8 章   继承和派生

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

8.8. 33 .. 1 派生类的构造函数1 派生类的构造函数

Page 24: 第 8 章   继承和派生

派生类构造函数的定义:派生类名 :: 派生类名(参数总表) : 基类名 1 (参数名表 1)《,基类名 2 (参数名表 2 ),……,基类名 n (参数名表 n )》,《成员对象名 1 (成员对象参数名表 1 ),……,成员对象名 m (成员对象参数名表 m )》 {……// 派生类新增成员的初始化;} // 所列出的成员对象名全部为新增成员对象的名字

注意: 在构造函数的声明中,冒号及冒号以后部分必须略去。 所谓不能继承并不是不能利用,而是把基类的构造函数作为新的构造函数的一部分,或者讲调用基类的构造函数。基类名仅指直接基类,写了底层基类,编译器认为出错。 冒号后的基类名,成员对象名的次序可以随意,这里的次序与调用次序无关。

Page 25: 第 8 章   继承和派生

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

Page 26: 第 8 章   继承和派生

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

Page 27: 第 8 章   继承和派生

【例【例 8.18.1 】由在册人员类公有派生学生类】由在册人员类公有派生学生类【例 8.1 】由在册人员类公有派生学生类。我们希望基类和派生类共享相同的公有接口 , 只能采用公有派生来实现。

基类:class Person{

string IdPerson; // 身份证号 ,18 位数字string Name; // 姓名Tsex Sex; // 性别 enum Tsex{mid,man,woman};int Birthday; // 生日 , 格式 1986年 8月 18 日写作 198608

18string HomeAddress; //家庭地址public:

Person(string, string,Tsex,int, string);// 构造函数 Person(); // 默认的构造函数 ~Person(); // 析构函数

Page 28: 第 8 章   继承和派生

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(); //输出个人信息};

// 接口函数:

Page 29: 第 8 章   继承和派生

派生的学生类 :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;};

验证主函数

Page 30: 第 8 章   继承和派生

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

Page 31: 第 8 章   继承和派生

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

Page 32: 第 8 章   继承和派生

8.4 8.4 多重继承与虚基类多重继承与虚基类• 8.4 .1 多重继承的二义性问题• 8.4. 2 虚基类的定义• 8.4. 3 虚基类的构造函数

Page 33: 第 8 章   继承和派生

8.8. 44 .1 .1 多重继承的二义性问多重继承的二义性问题题由多个基类共同派生出新的派生类,这样的继承结构被称为多重继承或多继承.

椅子 床

沙发 ( 单继承 ) 躺椅 ( 多重继承 )

两 用沙发 ( 多 重 继承 )图 8.2 椅子,床到两用沙发

多重继承:

Page 34: 第 8 章   继承和派生

在册人员

学生 ( 单继承)

教职工 ( 单继承)

兼职教师 ( 单继承 )

教师 ( 单继承)

行政人员 ( 单继承 )工人 ( 单继承 ) 研究生 ( 单继承)

行政人员兼教师( 多重继承 )

在职研究生( 多重继承 )研究生助教

( 多重继承 )

图 8.3 大学在册人员继承关系

派生出来的新类同样可以作为基类再继续派生出更新的类,依此类推形成一个层次结构。

Page 35: 第 8 章   继承和派生

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

Page 36: 第 8 章   继承和派生

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 类对象 EGStud1 ,并假定派生全部为公有派生,而 int No全为公有成员 :EGStud1.No // 在职学号EGStud1.GStudent::No //研究生号EGStud1.GStudent.Student::No // 学生号 EGStud1.GStudent.Student. Person::No // 身份证号EGStud1.Employee::No // 工作证号EGStud1.Employee.Person::No // 身份证号

两个身份证号从逻辑上讲应是一回事 ,但是物理上是分配了不同内存空间,是两个变量,请参见图7 .4(b) 。

Page 37: 第 8 章   继承和派生

Person

Person

Student

Employee

GStudent

EGStudent

Person 成员

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

Page 38: 第 8 章   继承和派生

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

注意:

Page 39: 第 8 章   继承和派生

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

【例【例 8.28.2 】由圆和高多重继承派生出圆锥】由圆和高多重继承派生出圆锥

检证主程序:

圆类 Circle定义高类 Line定义圆锥类 Cone定义

Page 40: 第 8 章   继承和派生

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

8.4.8.4. 2 虚基类的定义2 虚基类的定义

Page 41: 第 8 章   继承和派生

注意:virtual 关键字只对紧随其后的基类名起作用 :class Student:virtual public Person{...};class Employee:virtual public Person{...};

虚基类定义:class 派生类名 :virtual 继承方式 基类类名 {...};class 派生类名 : 继承方式 virtual 基类类名 {...};

Page 42: 第 8 章   继承和派生

图 8.5 采用虚基类后在职研究生类储存图

StudentGStudent

EGStudent

Person

Student 新成员GStudent 新成员Person

Employee 新 成员Person 成员EGStudent 新成员

Person

Person Employee虚拟继承

虚拟继承:

在 Person 的位置上放的是指针,两个指针都指向Person 成员存储的内存。这种使用虚基类的继承称为虚拟继承 ,

Page 43: 第 8 章   继承和派生

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

虚拟继承的构造函数:

Page 44: 第 8 章   继承和派生

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

构造函数执行次序:

Page 45: 第 8 章   继承和派生

【例 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;}

Page 46: 第 8 章   继承和派生

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

Page 47: 第 8 章   继承和派生

对照图8.5,尽管 Employee 和 Student 的构造函数都包含 Person 的构造函数,但并未真正调用。唯一的一次调用是在 EGStudent 构造函数中。如是非虚基类,则有两次调用。

【例 8.4】虚基类在多层多重继承中的应用 ——在职研究生类定义。以虚基类定义公有派生的学生类以虚基类定义公有派生的研究生类以虚基类定义公有派生的教职工类多重继承的以虚基类定义公有派生的在职研究生类

Page 48: 第 8 章   继承和派生
Page 49: 第 8 章   继承和派生

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

分析构造函数:第 8 章 继承和派生

Page 50: 第 8 章   继承和派生

Person::Person(){IdPerson="#";Name="#";Sex=mid;Birthday=0;HomeAddress="#";}

分析默认的构造函数:

分析析构函数:Person::~Person(){} //string 内部动态数组的释放,由 string 自带的析构函数完成

Page 51: 第 8 章   继承和派生

void Person::SetName(string name){Name=name; //拷入新姓名

}

修改名字 :

void Person::SetHomeAdd(string homeadd){HomeAddress=homeadd;

}

修改住址 :

Page 52: 第 8 章   继承和派生

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';}

输出个人信息 :

Page 53: 第 8 章   继承和派生

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;

}}

派生类构造函数 :

Page 54: 第 8 章   继承和派生

Student::Student(){// 基类默认的无参数构造函数不必显式给出int i ; NoStudent="";for(i=0;i<30;i++){ //课程与成绩清零 ,将来由键盘输入

cs[i].coursename="";cs[i].grade=0;

}}

Student::~Student(){} // 基类析构函数以及成员对象析构函数自动调用

默认派生类构造函数:

派生类析构函数:

Page 55: 第 8 章   继承和派生

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}

学生类课程设置函数:

Page 56: 第 8 章   继承和派生

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

查找学生课程成绩函数:

Page 57: 第 8 章   继承和派生

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;

}

打印学生情况函数:

Page 58: 第 8 章   继承和派生

例例 8.18.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");

Page 59: 第 8 章   继承和派生

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; if(!strcmp(temp,"end")) break; k=stu1.GetCourse(temp); if(k==-1)cout<<"未查到 "<<'\n'; else cout<<k<<'\n'; } return 0;}

Page 60: 第 8 章   继承和派生

【例【例 8.28.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);} //取圆周长 };

Page 61: 第 8 章   继承和派生

高类 Line:

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

Page 62: 第 8 章   继承和派生

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));}; //共有派生类中能直接访问直接基类的保护成员

派生类圆锥:

Page 63: 第 8 章   继承和派生

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';

}

检证主程序:

Page 64: 第 8 章   继承和派生

[[ 例例 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;}

Page 65: 第 8 章   继承和派生

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;}

以虚基类定义公有派生的研究生类

Page 66: 第 8 章   继承和派生

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;}

以虚基类定义公有派生的教职工类

Page 67: 第 8 章   继承和派生

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;}

多重继承的以虚基类定义公有派生的在职研究生类

Page 68: 第 8 章   继承和派生

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