1 / 21

Unit 7— 第八章 继承与多态

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

morse
Download Presentation

Unit 7— 第八章 继承与多态

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


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

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

  3. 8.2 派生类的构造函数与析构函数 派生类构造函数执行过程: Step 1. 调用基类构造函数,按它们在派生类定义中的先后,顺序调用; Step 2.调用成员对象的构造函数,按它们在类定义中声明的先后,顺序调用; Step 3. 派生类的构造函数体中的操作。 注意: (1)派生类构造函数中,只要不打算调用基类无参默认构造函数,都要显式给出基类名和参数表。 (2)若基类没有定义构造函数,则派生类也可不定义,全部采用系统给定的默认构造函数。 (3)若基类定义了带形参表的构造函数时,派生类就应当定义构造函数。

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

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

  6. 【H6_6】子女随父姓 • 基类 father(){cout<<"father默认构造函数调用"<<endl;surname="";firstname="";} ~father(){cout<<"father析构函数调用"<<endl; } string & getsurname(){return surname;}//取得姓名 void show(){cout<<surname<<firstname<<" 年龄:"<<age;} };

  7. 【H6_6】子女随父姓 借助对象(外部)间接访问保护数据。 可否内部直接访问基类的保护数据?怎么做? • 公有派生子女类 class child:publicfather{ 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; }

  8. 【H6_6】子女随父姓 • 公有派生子女类 ~child(){ cout<<"child析构函数调用"<<endl;} void show(){ cout<<"姓名:"; cout<<surname<< firstname<<" 年龄:"<<age; cout<<endl; cout<<"父亲:"; myfather.show(); cout<<endl; } };

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

  10. 【H6_6】子女随父姓 建立fa1 派生类调用基类默认构造函数 • 测试结果 father构造函数调用 father默认构造函数调用 child构造函数调用 子女信息结果: 姓名:欧阳智超 年龄:23 父亲:欧阳东海 年龄:50 child析构函数调用 father析构函数调用 father析构函数调用 father析构函数调用 成员对象myfather构造,调用了系统默认的复制构造函数,未有显示 派生类构造函数,建立ch1 析构派生类对象ch1 析构myfather成员对象 调用基类的析构函数 析构fa1

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

  12. 在册人员 教职工(单继承) 学生(单继承) 兼职教师(单继承) 教师(单继承) 工人(单继承) 行政人员(单继承) 研究生(单继承) 行政人员兼教师(多重继承) 研究生助教 (多重继承) 在职研究生 (多重继承) 8.3 多重继承与派生类成员标识(选读) 多重继承与类层次体系实例: 解决手段: 采用作用域分辨符“::” 基类名::成员名;//数据成员 基类名::成员名(参数表); //函数成员 歧义性问题: 由于继承,基类及其派生类均具有编号”(No) 这一成员,标识符相同,那么该如何区分和访问呢? 图8.3 大学在册人员继承关系

  13. 假定派生全部为公有派生,并且No全为保护成员,EGStudent类内部访问各No标识:假定派生全部为公有派生,并且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(); 假定派生全部为公有派生,并且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(); 不能连续使用多个“::” 每个继承路线各数据分配了不同的内存空间,是不同的变量,即便它们逻辑上是一回事(如身份证)。 图8.4 在职研究生派生类关系

  14. 8.4 虚基类(选读) 图8.4中的两个身份证号显然是不合理的。对此,可以把Person这个共同基类设置为虚基类,这样从不同路径继承来的Person类的同名数据成员(如身份证号)在内存中就是同一个数据。 虚基类(virtual base class)定义: class派生类名:virtual 访问限定符 基类类名{...}; 或者, class派生类名:访问限定符virtual 基类类名{...};

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

  16. 8.5 派生类应用讨论 一、派生类与基类 赋值兼容规则:任何需要基类对象的地方都可以用公有派生类的对象来代替。包括以下情况: 1.派生类对象可以赋值给基类对象:这时是把派生类对象中由基类继承来的成员赋值给基类对象。反过来不行,为什么? 因为派生类对象的新成员将无值可赋。 psl链表类指针对象, Istack链栈对象 psl=&istack; 2. 派生类对象取地址后赋给基类指针对象:只能通过这个指针访问派生类中由基类继承来的成员,不能访问派生类中的新成员。同样也不能反过来做。 psl=istack; 3. 派生类对象初始化基类的引用:引用是别名,但这个别名只包含派生类对象中的由基类继承来的成员。

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

  18. 例8.5 赋值兼容规则与自定义复制构造函数 Person和Student复制赋值操作符: 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; } 具体检验,参见课本!

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

  20. 多态性: 多态性是面向对象程序设计最显著、最核心的技术之一,可以实现同名操作(函数)的多义性(不同功能),提高代码复用性。C++中有两种多态性 : 8.6 多态性与虚函数 通过函数和运算符的重载实现,函数同名,参数表不同。 编译时多态性 (1)派生体系中成员函数名和参数表都相同,使得编译时无法确定该调用哪个函数,必须在程序执行过程中动态确定; (2)通过类继承关系和虚函数来实现。 运行时多态性

  21. 8.6 多态性与虚函数 8.6.1 虚函数的定义 8.6.2 纯虚函数 8.6.3 继承与多态的应用— 单链表派生类(选读) 8.6.4 动态绑定(选读)

More Related