21
Unit 7— 第第第 第第第第第 8.1 第第第第第第第第 8.4 第第第 第第第 () 8.3 第第第第第第第第第第第第 ( 第第 ) 8.6 第第第第第第第 8.5 第第第第第第第 8.2 第第第第第第第第第第第第第

Unit 7— 第八章 继承与多态

  • Upload
    morse

  • View
    139

  • Download
    0

Embed Size (px)

DESCRIPTION

Unit 7— 第八章 继承与多态. 8.1 继 承与派生的概念 . 8.4 虚 基类 (选读) . 8.5 派生类应用讨论 . 8.2 派生类的构造函数与析构函数 . 8.3 多重继承与派生类成员标识 ( 选读 ). 8.6 多态性与虚函数 . 8.2 派生类的构造函数与析构函数. 派 生类构造函数的定义: - PowerPoint PPT Presentation

Citation preview

Page 1: Unit 7— 第八章 继承与多态

Unit 7— 第八章 继承与多态8.1 继承与派生的概念 8.4 虚基类 (选读)

8.3 多重继承与派生类成员标识 ( 选读 ) 8.6 多态性与虚函数

8.5 派生类应用讨论 8.2 派生类的构造函数与析构函数

Page 2: Unit 7— 第八章 继承与多态

派生类构造函数的定义:派生类名 :: 派生类名(参数总表) : 基类名 1 (参数名表1 )《,基类名 2 (参数名表 2 ),……,基类名 n (参数名表 n )》,《成员对象名 1 (成员对象参数名表 1 ),……,成员对象名 m (成员对象参数名表 m )》{ …… // 派生类新增或更新成员的初始化;}; 注意:( 1 )构造函数的声明中,冒号及冒号以后部分必须略去。( 2 )基类的构造函数尽管未被继承,但会被派生类构造函数所调用。这里的基类名仅指直接基类,写了更底层基类,编译报错。( 3 )参数总表中参数需有类型说明,而各参数名表中参数则无。

8.2 派生类的构造函数与析构函数

所列成员对象名均为新增的 ;指针型成员对象如何处理?

Page 3: Unit 7— 第八章 继承与多态

派生类构造函数执行过程: Step 1. 调用基类构造函数,按它们在派生类定义中的先后,顺序调用 ; Step 2. 调用成员对象的构造函数,按它们在类定义中声明的先后,顺序调用 ;Step 3. 派生类的构造函数体中的操作。

8.2 派生类的构造函数与析构函数

注意:( 1 )派生类构造函数中,只要不打算调用基类无参默认构造函数,都要显式给出基类名和参数表。( 2 )若基类没有定义构造函数,则派生类也可不定义,全部采用系统给定的默认构造函数。( 3 )若基类定义了带形参表的构造函数时,派生类就应当定义构造函数。

Page 4: Unit 7— 第八章 继承与多态

析构函数:1. 功能:派生类析构函数的功能依然用于善后。( 1 )只需在函数体内把派生类新增的一般成员处理好;( 2 )新增成员对象和基类的善后,由系统调用成员对象和基类的析构函数来完成。2. 执行:析构函数各部分执行次序与构造函数相反Step1. 对派生类新增一般成员善后;Step2. 对新增成员对象析构 ;Step3. 对基类对象析构。

8.2 派生类的构造函数与析构函数

Page 5: Unit 7— 第八章 继承与多态

【 H6_6】子女随父姓基类class father{protected:

string surname;// 姓string firstname;// 名int age;

public:father(string & surn,string & firn,int a){

cout<<"father 构造函数调用 "<<endl;surname=surn;firstname=firn;age=a;}

Page 6: Unit 7— 第八章 继承与多态

【 H6_6】子女随父姓基类father(){cout<<"father 默认构造函数调用 "<<endl;surname="";firstname="";}

~father(){cout<<"father 析构函数调用 "<<endl;}

string & getsurname(){return surname;}// 取得姓名void show(){cout<<surname<<firstname<<" 年龄 :"<<age;}

};

Page 7: Unit 7— 第八章 继承与多态

【 H6_6】子女随父姓 公有派生子女类class child:public father{private:

father myfather;// 成员对象public:

child(){cout<<"child 构造函数调用 "<<endl;}child(father& fa,string & na,int a):father(),myfather(fa){

cout<<"child 构造函数调用 "<<endl;surname=fa.getsurname();firstname=na;age=a; }

借助对象(外部)间接访问保护数据。可否内部直接访问基类的保护数据?怎么做?

Page 8: Unit 7— 第八章 继承与多态

【 H6_6】子女随父姓 公有派生子女类 ~child(){

cout<<"child 析构函数调用 "<<endl;}

void show(){cout<<" 姓名 :"; cout<<surname<<

firstname<<" 年龄 :"<<age;cout<<endl;cout<<" 父亲 :"; myfather.show();cout<<endl;

}};

Page 9: Unit 7— 第八章 继承与多态

【 H6_6】子女随父姓 测试int main(){

string fasurn1(" 欧阳 "),fafirn1(" 东海 ");string chfirn1(" 智超 ");father fa1(fasurn1,fafirn1,50);child ch1(fa1,chfirn1,23);cout<<" 子女信息结果 :"<<endl;ch1.show();return 0;

}

Page 10: Unit 7— 第八章 继承与多态

【 H6_6】子女随父姓 测试结果father 构造函数调用 father 默认构造函数调用

child 构造函数调用 子女信息结果 :姓名 : 欧阳智超 年龄 :23父亲 : 欧阳东海 年龄 :50child 析构函数调用 father 析构函数调用father 析构函数调用 father 析构函数调用

建立 fa1派生类调用基类默认构造函数

成员对象 myfather 构造,调用了系统默认的复制构造函数,未有显示派生类构造函数,建立 ch1

析构派生类对象 ch1析构 myfather 成员对象调用基类的析构函数析构 fa1

Page 11: Unit 7— 第八章 继承与多态

8.2 派生类的构造函数与析构函数 注意:( 1 )例中 string 类字符串用作 father 类的成员对象(聚合),字符数组的动态内存分配和释放封中在 string 的构造和析构函数中,使用方便。而采用动态建立 C 风格字符串,则要自己解决深复制问题。( 2 )提倡完善的类对象封装,不仅封装数据和对数据的操作,而且封装资源的动态分配与释放,形成一个完备的子系统,如 string 类字符串。( 3 )聚合(与组合)是一种完善的封装,因为借助成员对象将资源的动态分配与释放封装其内,大大简化了层次化的类派生体系中资源的动态分配与释放的操作,不必再考虑复杂的多层深复制。

Page 12: Unit 7— 第八章 继承与多态

在册人员

学生 (单继承 )教职工 (单继承 )兼职教师 ( 单继承 )

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

行政人 员兼教师 ( 多重继承 )在职研究生( 多 重 继承 )

研究生助教( 多重继承 )

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

8.3 多重继承与派生类成员标识(选读)多重继承与类层次体系实例:

歧义性问题 :由于继承,基类及其派生类均具有编号”( No ) 这一成员,标识符相同,那么该如何区分和访问呢?

解决手段:采用作用域分辨符“ ::”基类名 :: 成员名 ;// 数据成员基类名 :: 成员名(参数表) ; // 函数成员

Page 13: Unit 7— 第八章 继承与多态

图 8.4 在职研究生派生类关系

假定派生全部为公有派生,并且 No 全为公有成员, EGStudent 类对象 egstd1(外部)访问各 No 标识 :egstd1.No // 在职学号egstd1.GStudent::No //研究生号egstd1.GStudent.Student::No//学生号 egstd1.GStudent.Student.Person::No //身份证号egstd1.Employee::No //工作证号egstd1.Employee.Person::No //身份证号no=egstd1.Employee.Person::GetNo();

每个继承路线各数据分配了不同的内存空间,是不同的变量,即便它们逻辑上是一回事(如身份证)。

不能连续使用多个“ ::”

假定派生全部为公有派生,并且 No 全为保护成员, EGStudent 类内部访问各 No标识 :egstd1.No // 在职学号egstd1.GStudent::No //研究生号egstd1.GStudent.Student::No//学生号 egstd1.GStudent.Student.Person::No //身份证号egstd1.Employee::No //工作证号egstd1.Employee.Person::No //身份证号no=egstd1.Employee.Person::GetNo();

class EGStudent

int No在职学号………

class GStudent

int No研究生号 ……….

class Student

int No学生号 ……….

class Person

int No身份证号 ……….

class Employee

int No工作证号 ……….

class Person

int No身份证号 ……….

Page 14: Unit 7— 第八章 继承与多态

图 8.4 中的两个身份证号显然是不合理的。对此,可以把 Person 这个共同基类设置为虚基类,这样从不同路径继承来的 Person 类的同名数据成员(如身份证号)在内存中就是同一个数据。

8.4 虚基类(选读)

虚基类 (virtual base class) 定义:class 派生类名 :virtual 访问限定符 基类类名{...};或者,class 派生类名 : 访问限定符 virtual 基类类名{...};

Page 15: Unit 7— 第八章 继承与多态

8.4 虚基类(选读)派生类名 :: 派生类名 ( 参数总表 ): 基类名 1( 参数名表1) 《 , 基类名 2( 参数名表 2),……, 基类名 n( 参数名表n) 》 , 《成员对象名 1( 成员对象参数名表 1),……, 成员对象名 m( 成员对象参数名表 m) 》,底层虚基类名1( 参数名表 1) 《 ,……, 底层虚基类名 r( 参数名表r) 》 {……// 派生类新增或替换的成员的初始化}; 注意:在多层虚拟继承构造函数中,不仅要列出直接虚基类,还要列出间接的底层虚基类。

虚拟继承的构造函数:

Page 16: Unit 7— 第八章 继承与多态

因为派生类对象的新成员将无值可赋。

一、派生类与基类 赋值兼容规则:任何需要基类对象的地方都可以用公有派生类的对象来代替。包括以下情况:

8.5 派生类应用讨论

1. 派生类对象可以赋值给基类对象:这时是把派生类对象中由基类继承来的成员赋值给基类对象。反过来不行,为什么?2. 派生类对象取地址后赋给基类指针对象:只能通过这个指针访问派生类中由基类继承来的成员,不能访问派生类中的新成员。同样也不能反过来做。3. 派生类对象初始化基类的引用:引用是别名,但这个别名只包含派生类对象中的由基类继承来的成员。

psl链表类指针对象 ,Istack链栈对象

psl=&istack;psl=istack;

Page 17: Unit 7— 第八章 继承与多态

Person 和 Student 复制构造函数:例 8.5 赋值兼容规则与自定义复制构造函数

Person::Person(Person &ps){

IdPerson = ps.IdPerson;Name = ps.Name;Sex = ps.Sex;Birthday = ps.Birthday;HomeAddress = ps.HomeAddress;

}

Student::Student(Student &Std) : Person(Std){ // 按赋值兼容规则 Std 可为 Person实参

NoStudent=Std.NoStudent;for ( int i = 0;i<30;i++ ){

cs[i].coursename = Std.cs[i].coursename;cs[i].grade = Std.cs[i].grade;

}}

Page 18: Unit 7— 第八章 继承与多态

Person & Person::operator=(Person &ps){IdPerson=ps.IdPerson;Name=ps.Name;Sex=ps.Sex;Birthday=ps.Birthday;HomeAddress=ps.HomeAddress;return *this;

}

Student & Student::operator = (Student &Std){this->Person::operator = (Std); // 注意标准格式NoStudent=Std.NoStudent;for ( int i=0; i<30; i++){

cs[i].coursename=Std.cs[i].coursename;cs[i].grade=Std.cs[i].grade;

}return *this;

}

例 8.5 赋值兼容规则与自定义复制构造函数Person 和 Student 复制赋值操作符:

具体检验,参见课本!

Page 19: Unit 7— 第八章 继承与多态

二、继承与聚合派生类通过继承可以使用基类的成员;要获得类似的效果,也可以把一个类的对象作为新类的对象成员,即聚合。1. 概念 B 类中包含 A 类指针对象,称为聚合,而 B 类中包含 A 类对象,称为组合,可简单地统称为聚合。2. 聚合与继承比较( 1 )派生类对基类只能直接继承一次,否则,即便使用域分辨符成员名也无法区分;而聚合类中安排多个成员对象也不出现该困惑。( 2 )成员对象体现了封装更深层的内涵:在派生类和它的基类中最好不要有内存的动态分配;动态分配和释放应该封装在成员对象(构造和析构)中,该成员对象中也提供深复制。如同类 string ,程序员可以放心使用。( 3 )继承结合虚函数可以实现运行时多态性,聚合做不到。

8.5 派生类应用讨论

聚合类并不能直接访问被聚合类的保护成员,派生则可直接访问基类保护成员!

Page 20: Unit 7— 第八章 继承与多态

8.6 多态性与虚函数多态性: 多态性是面向对象程序设计最显著、最核心的技术之一,可以实现同名操作(函数)的多义性(不同功能),提高代码复用性。 C++ 中有两种多态性 :编译时多态性

运行时多态性 ( 1 )派生体系中成员函数名和参数表都相同,使得编译时无法确定该调用哪个函数,必须在程序执行过程中动态确定;( 2 )通过类继承关系和虚函数来实现。

通过函数和运算符的重载实现,函数同名,参数表不同。

Page 21: Unit 7— 第八章 继承与多态

8.6.1 虚函数的定义

8.6.4 动态绑定 (选读)

8.6.2 纯虚函数 8.6.3 继承与多态的应用—单链表派生类(选读)

8.6 多态性与虚函数