Unit 7— 第八章 继承与多态

Preview:

DESCRIPTION

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

Citation preview

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

};

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

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

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

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

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

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

}};

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

}

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

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

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

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

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

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

在册人员

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

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

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

研究生助教( 多重继承 )

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

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

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

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

图 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身份证号 ……….

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

8.4 虚基类(选读)

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

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

虚拟继承的构造函数:

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

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

8.5 派生类应用讨论

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

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

psl=&istack;psl=istack;

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;

}}

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 复制赋值操作符:

具体检验,参见课本!

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

8.5 派生类应用讨论

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

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

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

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

8.6.1 虚函数的定义

8.6.4 动态绑定 (选读)

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

8.6 多态性与虚函数

Recommended