第7单元 面向对象编程-继承与多态-2.ppt
Unit 7Unit 7第八章第八章 继承与多态继承与多态8.1 8.1 继继承与派生的概念承与派生的概念 8.4 8.4 虚虚基类基类 (选读)(选读)8.3 8.3 多重继承与派生类成员标识多重继承与派生类成员标识(选读选读)8.6 8.6 多态性与虚函数多态性与虚函数 8.5 8.5 派生类应用讨论派生类应用讨论 8.2 8.2 派生类的构造函数与析构函数派生类的构造函数与析构函数 派派生类构造函数的定义:生类构造函数的定义:派生类名派生类名:派生类名(参数总表)派生类名(参数总表):基类名基类名1 1(参数名表(参数名表1 1),基类名基类名2 2(参数名表(参数名表2 2),),基类名,基类名n n(参数名表(参数名表n n),成员对象名成员对象名1 1(成员对象参数名表(成员对象参数名表1 1),),成员对象,成员对象名名m m(成员对象参数名表(成员对象参数名表m m)/派生类派生类新新增或更新增或更新成成员的初始化;员的初始化;;注意:注意:(1 1)构造函数的声明中,冒号及冒号以后部分必须略去。)构造函数的声明中,冒号及冒号以后部分必须略去。(2 2)基类的构造函)基类的构造函数尽管未被继承,但会被派生类构造函数数尽管未被继承,但会被派生类构造函数所所调调用。用。这里的这里的基基类名仅指类名仅指直接基类,写直接基类,写了更底了更底层基类,编层基类,编译报错译报错。(3 3)参数总)参数总表中参数需表中参数需有类型说明有类型说明,而各参数名表中参数则,而各参数名表中参数则无无。8.2 派生类的构造函数与析构函数所列成员对象名所列成员对象名均均为新增的为新增的;指指针型成员对象如何处理?针型成员对象如何处理?派派生类构造函生类构造函数执行过程:数执行过程:Step 1.Step 1.调调用基类构造函数,按它们在派生类定用基类构造函数,按它们在派生类定义义中的中的先先后,后,顺序调顺序调用用;Step 2.Step 2.调用成员对象的构造函数,按它们在类定调用成员对象的构造函数,按它们在类定义中声明的先义中声明的先后,顺序调用后,顺序调用;Step 3.Step 3.派派生类的构造函数体中的操作。生类的构造函数体中的操作。8.2 派生类的构造函数与析构函数注意:注意:(1 1)派)派生类构造函数中,只生类构造函数中,只要不打算调用基类无要不打算调用基类无参默参默认构造函认构造函数,都数,都要显式给出基类名和参数表要显式给出基类名和参数表。(2 2)若基)若基类没有定义构造函数,则派生类也类没有定义构造函数,则派生类也可不可不定义,全部采用系统给定的默认构造函数。定义,全部采用系统给定的默认构造函数。(3 3)若基)若基类定义了类定义了带形带形参表的构造函数时,派生参表的构造函数时,派生类就应当定义构造函数。类就应当定义构造函数。析析构函数:构函数:1 1.功能:派生功能:派生类析构函数的功类析构函数的功能依然用于善后。能依然用于善后。(1 1)只需在函数体内把派生类新增的一般成员处理只需在函数体内把派生类新增的一般成员处理好;好;(2 2)新增成新增成员对象和基类的善员对象和基类的善后,由系统调后,由系统调用成员用成员对象和基类的析构函数来完成对象和基类的析构函数来完成。2.2.执行:析执行:析构函数各部分执行次序与构造函数相构函数各部分执行次序与构造函数相反反Step1.Step1.对对派生类新增一般成派生类新增一般成员员善善后;后;Step2.Step2.对对新新增成员对象析构增成员对象析构;Step3.Step3.对对基基类对象析类对象析构构。8.2 派生类的构造函数与析构函数【H6_6】子女随父姓基基类类class fatherprotected:string surname;/姓姓string firstname;/名名int age;public:father(string&surn,string&firn,int a)coutfather构造函数调用构造函数调用endl;surname=surn;firstname=firn;age=a;【H6_6】子女随父姓基基类类father()coutfather默认构造函数调用默认构造函数调用endl;surname=;firstname=;father()coutfather析构函数调用析构函数调用endl;string&getsurname()return surname;/取得姓取得姓void show()coutsurnamefirstname 年龄年龄:age;【H6_6】子女随父姓 公有派生子女类公有派生子女类class child:public fatherprivate:father myfather;/成员对象成员对象public:child(father&fa,string&na,int a):father(),myfather(fa)coutchild构造函数调用构造函数调用endl;surname=myfather.getsurname();firstname=na;age=a;借助对象(外部)间接访问借助对象(外部)间接访问保护数据。保护数据。可否内部直接访问基类的保可否内部直接访问基类的保护数据?怎么做?护数据?怎么做?【H6_6】子女随父姓 公有派生子女类公有派生子女类 child()coutchild析构函数调用析构函数调用endl;void show()cout姓名姓名:;coutsurnamefirstname 年龄年龄:age;coutendl;cout父亲父亲:;myfather.show();coutendl;【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成员对象成员对象调用基类的析构函数调用基类的析构函数析构析构fa18.2 派生类的构造函数与析构函数 注注意:意:(1 1)例中)例中stringstring类字类字符符串用作串用作fatherfather类的成类的成员对员对象象(聚(聚合)合),stringstring类字符串封装了字符数组的动类字符串封装了字符数组的动态内态内存分存分配和释放、深复制,使用安全、方便。而采用配和释放、深复制,使用安全、方便。而采用动态建立动态建立C C风格字符风格字符串,则要自己解决深串,则要自己解决深复复制问题。制问题。(2 2)提)提倡完善的类对象封装,不仅封装数据和对数倡完善的类对象封装,不仅封装数据和对数据的操作,据的操作,而且封装资源的动态分配与释放而且封装资源的动态分配与释放,形成,形成一个完备的子系一个完备的子系统,如统,如stringstring类类字字符符串。串。(3 3)聚合是)聚合是一种完善的封一种完善的封装,因为借助成员对象将装,因为借助成员对象将资源的动态分配与释资源的动态分配与释放封装其内,放封装其内,大大大简大简化了层次化了层次化的类派生体化的类派生体系中资源的动态分配与释系中资源的动态分配与释放的操作,放的操作,不必再考虑复杂的多层深不必再考虑复杂的多层深复制复制。在册人员在册人员学生学生(单继承单继承)教职工教职工(单继承单继承)兼职教师兼职教师(单继承单继承)教师教师(单继承单继承)行政人员行政人员(单继承单继承)工人工人(单继承单继承)研究生研究生(单继承单继承)行行政政人人员员兼兼教教师师(多重继承多重继承)在职研究生在职研究生(多重继承多重继承)研究生助教研究生助教(多重继承多重继承)图图8.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(参数名表参数名表1)1),基基类名类名2(2(参数名表参数名表2),2),基类名基类名n(n(参数名表参数名表n)n),成员对象成员对象名名1(1(成员对象参数名表成员对象参数名表1),1),成员对象名成员对象名m(m(成员对象参成员对象参数名表数名表m)m),底层虚基类名,底层虚基类名1(1(参数名表参数名表1)1),底层虚底层虚基类名基类名r(r(参数名表参数名表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)/按赋值兼容规则按赋值兼容规则StdStd可为可为PersonPerson实参实参NoStudent=Std.NoStudent;for(int i=0;iPerson:operator=(Std);/注意标准格式注意标准格式NoStudent=Std.NoStudent;for(int i=0;i30;i+)csi.coursename=Std.csi.coursename;csi.grade=Std.csi.grade;return*this;例8.5 赋值兼容规则与自定义复制构造函数Person和和Student复复制赋值操作符:制赋值操作符:具体检验,参见课本!具体检验,参见课本!二二、继承与聚合、继承与聚合派生派生类通过继承可以使用基类通过继承可以使用基类的成类的成员;要获得类似的效果,也员;要获得类似的效果,也可以把一个类可以把一个类的对象作的对象作为新为新类的对象成类的对象成员,即员,即聚合聚合。1.1.概念细化概念细化 B类中包含类中包含A类类指针对象指针对象,称为,称为B聚合聚合A,而,而B类中包含类中包含A类类对象对象,称为,称为B组合组合A,可简单地统称为,可简单地统称为B聚合聚合A。聚合与继承比较聚合与继承比较(1)派生类对基类只能)派生类对基类只能直接继承一次直接继承一次,否则,即便使,否则,即便使用域分辨用域分辨符成员名也无法区分;而聚合类中符成员名也无法区分;而聚合类中安排多个成员对象安排多个成员对象也不出现也不出现该困惑。该困惑。(2)成)成员对象体现了封装更深员对象体现了封装更深层的内涵:层的内涵:在在派生类和它的派生类和它的基类基类中最好不要有中最好不要有内存的动态分内存的动态分配;动配;动态分态分配和释放应配和释放应该封装在成该封装在成员对员对象(构造和析构)中,该象(构造和析构)中,该成员对象成员对象中也提中也提供深复制供深复制。如同。如同类类string,程,程序序员可员可以放以放心使用。心使用。1.(3)继继承结合虚承结合虚函函数可以实现数可以实现运行时多态性运行时多态性,聚合做不到。,聚合做不到。8.5 8.5 派生类应用讨论派生类应用讨论聚聚合类并合类并不能直接访不能直接访问问被聚合类的被聚合类的保护成保护成员员,派生则派生则可可直接直接访访问基类保护成员问基类保护成员!8.6 多态性与虚函数多多态性:态性:多态性是面向对象程序设多态性是面向对象程序设计最显著、最核心的技计最显著、最核心的技术之术之一,一,可可以实现同名操作(函数)的多义性(不同功能),以实现同名操作(函数)的多义性(不同功能),提高代码复用性。提高代码复用性。C+中有两种多态性中有两种多态性:编译编译时多时多态性态性 运行时多态性运行时多态性 (1 1)派生体系中成员函)派生体系中成员函数名和数名和参参数表都相同,使得编译时无法数表都相同,使得编译时无法确确定该调用定该调用哪个哪个函数,必须在程函数,必须在程序执行过序执行过程中动态确定;程中动态确定;(2 2)通)通过类继承关系和虚函数过类继承关系和虚函数来实来实现。现。通过函数和运算符的重载实现,通过函数和运算符的重载实现,函数同名,参数表不同。函数同名,参数表不同。8.6.1 虚函数的定义虚函数的定义 8.6.4 动态绑定动态绑定(选读)(选读)8.6.2 纯虚函数纯虚函数 8.6.3 继承与多态的应用继承与多态的应用单链表派生类(选读)单链表派生类(选读)8.6 多态性与虚函数