570 likes | 690 Views
Object-Oriented Programming in C++ 第五章 继承与派生. 中国科大学继续教育学院 李艺 leeyi@ustc.edu.cn. 第一 章 C++ 的初步知识 第二章 类和对象 第三章 再论类和对象 第四章 运算符重载 第五章 继承与派生 第六章 多态性与虚函数 第七章 输入输出流 第八章 C++ 工具. 5.1 继承与派生的概念 5.2 派生类的声明方式 5.3 派生类的构成 5.4 派生类成员的访问属性 5.5 派生类的构造函数和析构函数 5.6 多重继承 5.7 基类与派生类的转换
E N D
Object-OrientedProgramming in C++第五章 继承与派生 中国科大学继续教育学院 李艺 leeyi@ustc.edu.cn
第一章 C++的初步知识 第二章 类和对象 第三章 再论类和对象 第四章 运算符重载 第五章 继承与派生 第六章 多态性与虚函数 第七章 输入输出流 第八章 C++工具
5.1 继承与派生的概念 5.2 派生类的声明方式 5.3 派生类的构成 5.4 派生类成员的访问属性 5.5 派生类的构造函数和析构函数 5.6 多重继承 5.7 基类与派生类的转换 5.8 继承与组合
5.1 继承与派生的概念 我们知道,面向对象程序设计的4个特性有抽象、封装、继承和多态。我们已经讨论了数据的抽象性和封装性,现在来讨论数据的继承。
大学学生 大学生 研究生 大专生 本科生 硕士生 博士生 5.1 继承与派生的概念 所谓继承,就是根据一个已存在的类建立一个新的类。已存在的类叫“基类”或“父类”,新建立的类叫“派生类”或“子类”。 新建立的子类从基类那里获得基类的特性,称为类的继承;由基类产生新的子类,叫派生。一个基类可以派生多个子类,一个子类可以由多级基类派生而来。比如: 一个基类派生多级子类示意图 约定:箭头方向表示继承的方向,由派生类指向基类。
研究生 职工 在职研究生 5.1 继承与派生的概念 • 多个基类派生一个子类示意图。约定:箭头方向表示继承的方向,由派生类指向基类。 基类: 派生类:
5.2 派生类的声明方式 • 声明派生类的方法: class 派生类名:[ 继承方式 ] 基类名 { 派生类新增的成员 }; • 继承方式包括: • Public方式,公用方式; • Private方式,私有方式; • Protected方式,受保护方式。 继承方式是可选的,不写此项,默认为私有方式。
5.2 派生类的声明方式 例如,由一基类Box(盒子类),派生出一个新的子类colorBox(彩色盒子): #include <iostream.h> class Box {private: int len,wid,hei; public: void display( ) { cout<<“盒子体积=” <<len*wid*hei<<endl; } Box (int l=0, int w=0, int h=0) { len = l; wid = w; hei = h; } }; class colorBox : public Box { private: int color; public: void display( ) {cout<<“盒子的颜色=” << color << endl; } colorBox (int c=0) {color = c; } }; void main( ) { colorBox cb1(3); cb1.display( ); }
colorBox类 int len; int wid; int hei; 构造函数Box; void display( ); Box类 int len; int wid; int hei; 构造函数Box; void display( ); int color; void display( ); 5.3 派生类的构成 派生类的成员包括从基类继承过来的成员和自己增加的成员。从基类继承过来的成员,体现了基类与子类的共性;而新增加的成员,体现了子类的个性。下面是Box基类和 colorBox子类的存储图: 基类 派生类 数据成员 继承 成员 函数 新增成员
5.3 派生类的构成 请注意,类的继承并不是把基类成员和派生类新增成员简单地放在一起。构造一个派生类包括以下四部分工作 • 从基类接收成员。派生类把基类的全部成员(不包括构造函数和析构函数)接收过来。不足之处在于会造成冗余,浪费存储空间和执行效率,尤其是多次派生时。所以,不能随意地找一个类去派生出某一个子类。 • 调整从基类接收的成员。接收基类成员必须照单全收,但编程人员可以对接收过来的成员作一些调整。如, • 通过指定不同的继承方式,来改变基类成员在派生类中的访问属性; • 在派生类中声明一个和基类成员同名的成员,来“遮盖”接收的基类成员。如果想遮盖成员函数,其函数名和参数表必须相同,否则就成为重载。 • 在声明派生类时增加新的成员。主要体现子类对基类的扩展。 • 构造派生类的构造函数和析构函数。
5.4 派生类成员的访问属性 派生类成员中包含基类成员和派生类成员,在讨论访问属性时,需要考虑以下几种情况: • 基类成员函数访问基类成员; • 派生类成员函数访问派生类自己新增成员; • 基类成员函数访问派生类成员; • 派生类成员函数访问基类成员; • 在派生类外访问派生类的成员; • 在派生类外访问基类的成员。 对于①、②,种情况,按照第二章介绍的规则处理。即基类成员函数可访问基类成员,派生类成员函数可访问派生类自己新增成员,基类私有数据成员只能被基类成员函数访问,派生类新增私有数据成员,只能被派生类新增成员函数访问。
5.4 派生类成员的访问属性 对于第④~⑥种情况,我们分不同的继承方式分开讨论: • 公用继承:基类的公用成员和保护成员在派生类中保持原有的访问属性,其私有成员仍为基类的私有,派生类新增成员不可访问它。 • 私有继承:基类的公用成员和保护成员在派生类中成为私有成员,其私有成员仍为基类的私有,派生类新增成员不可访问它。 • 保护继承:基类的公用成员和保护成员在派生类中成为保护成员,其私有成员仍为基类的私有,派生类新增成员不可访问它。 • 什么叫保护成员? 保护成员不能被外界引用,但可以被派生类的成员引用。
5.4 派生类成员的访问属性 • 公用继承 • 公用继承的定义:在定义一个派生类时,将基类的继承方式指定为 public 就是公用继承。其基类叫公用基类( public base class), 子类叫公用派生类 ( public derived class)。 • 被公用继承而来的基类在派生类中的访问属性见下表: 我们来看看下面的示例程序:
5.4 派生类成员的访问属性 #include <iostream.h> #include <string.h> class person {public: void get_value( ) { cin>>num>>name; } void display( ) { cout<<“id:”<<num<<endl; cout<<“name:”<<name<<endl; } private: int id; string name; }; class student : public person {public: void display( ) {cout<<“id:”<<num<<endl; // 错误, 派生类中不能访问基类私有成员 cout<<“name:”<<name<<endl; // 错误, 派生类中不能访问基类私有成员 cout<<“stu_id:”<<stu_id<<endl; } private: int stu_id; };
程序应修改,在main函数中分别调用基类和派生类的显示成员函数: #include <iostream.h> #include <string.h> class person {public: void get_value( ) { cin>>num>>name; } void person_display( ) { cout<<“id:”<<num<<endl; cout<<“name:” <<name<<endl; } private: int id; string name; }; class student : public person {public: void student_display( ) { cout<<“stu_id:” <<stu_id<<endl; } private: int stu_id; }; void main( ) { student zhangsan; : : : zhangsan.person_display( ); zhangsan.student_display( ); } 这里只是对显示函数做了调整,其实还有很多修改. 比如,对数据输入, 就要调整. 请同学们自己完成.
5.4 派生类成员的访问属性 • 私有继承 • 私有继承的定义:在定义一个派生类时,将基类的继承方式指定为 private 就是私有继承。其基类叫私有基类( privatepublic base class), 子类叫私有派生类 ( private derived class)。 • 被私有继承而来的基类在派生类中的访问属性见下表: 我们来看看下面的示例程序:
基类A 子类B 子类B public: int i; private: int k; public: int i; private: int k; public: int m; private: int i,n; 私有继承 public: int m; private: int n; 不可访问: int k ; 派生类新 增成员 5.4 派生类成员的访问属性 我们作如下示意图来说明私有继承的访问属性: 实际派生类 • 基类A中的公用成员i ,私有成员 k,被私有派生给子类B • 子类B自己新增公用成员 m,和私有成员n • 最后,子类B具有的公用成员为 m,私有成员为 i, n。基类的k 在子类B中不可访问。
私有继承示例:这里的错误该如何改写? #include <iostream.h> #include <string.h> class person {public: void get_value( ) { cin>>num>>name; } void person_display( ) { cout<<“id:”<<num<<endl; cout<<“name:” <<name<<endl; } private: int id; string name; }; class student : private person {public: void student_display( ) { cout<<“stu_id:” <<stu_id<<endl; } private: int stu_id; }; void main( ) { student zhangsan; : : : zhangsan.person_display( ); // 私有继承基类的公用函数 // 变成子类私有函数,类外不能访问 zhangsan.student_display( ); }
可以将子类的显示函数这样改写: #include <iostream.h> #include <string.h> class person {public: void get_value( ) { cin>>num>>name; } void person_display( ) { cout<<“id:”<<num<<endl; cout<<“name:” <<name<<endl; } private: int id; string name; }; class student : private person {public: void student_display( ) { person_display( ); cout<<“stu_id:” <<stu_id<<endl; } private: int stu_id; }; void main( ) { student zhangsan; : zhangsan.student_display( ); }
5.4 派生类成员的访问属性 • 保护成员和保护继承 “保护,protected”既可以用来限定类成员的性质,也可以用来限定继承方式。 • 保护成员:被protected声明的成员叫保护成员,保护成员不能被类外访问,这和私有成员的特性一样;但保护成员如果被派生给子类,可以被子类的成员函数引用。 我们将友元比喻成朋友,可以进入自己的卧室,访问类的私有成员;我们将保护成员比喻成保险箱,任何外人不得窥视,包括朋友。只有自己和子女(即派生类)可以打开。 • 保护继承:派生类继承时被指定为 protected 。用保护方式继承的派生类叫保护派生类( protected detived class),其基类叫受保护的基类( protected bass class )。 保护继承的特点是,保护基类的公用成员和和保护成员在保护派生类中成为保护成员,私有成员仍为基类私有,派生类不得引用。
5.4 派生类成员的访问属性 综合所述,三种继承方式下派生类成员的访问属性列表如下: 由上表可以得出以下结论: • 公用成员,派生类内外都可以访问; • 保护成员,派生类内可以访问,派生类外不能访问,其下一级的派生也可以访问; • 私有成员,派生类内可以访问,派生类外不能访问,其下一级的派生不可访问; • 不可访问的成员,派生类内外都不可访问。
5.4 派生类成员的访问属性 下面是一个怎样使用保护成员的示例: #include <iostream.h> #include <string.h> class person {protected: int id; string name; }; class student : protected person {public: void student_display( ) { cout<<“id:”<<id<<endl; cout<<“name:”<<name<<endl; cout<<“stu_id:” <<stu_id<<endl; } private: int stu_id; }; void main( ) { student zhangsan; student.id=“12345678; //错误,外界不能访问保护成员 zhangsan.student_display( ); // 合法,此函数是公用函数 }
class A {public: int i; protected: int j; void f2( ); private: int k; }; class B : public A {public: void f3( ); protected: void f4( ); private: int m; }; class C : protected B {public: void f5( ); private: int n; }; 5.4 派生类成员的访问属性 • 多级派生时的访问属性 实际应用中,常常有多级派生的情况。比如:A 是基类,派生出B类,B类又派生出C类。则B是A的直接派生,C是A的间接派生。A是B的直接基类,A是C的间接基类。请看: A是B的公用基类,B是C的保护基类;各成员在不同类的属性为:
5.4 派生类成员的访问属性 上表说明: • 无论哪一种继承方式,在派生类中不能访问基类的私有成员,私有成员只能被本类的成员函数访问; • 多级派生若都采用公用继承,直到最后一级派生类都能访问基类的公用成员和保护成员; • 多级派生若都采用私有继承,经过若干次派生后,基类的所有成员都变成不可访问; • 多级派生若都采用保护继承,派生类外无法访问派生类中的任何成员,人们很难记住哪些成员还能访问,哪些成员不能被访问。 因此,实际应用中,通常采用公用继承方式。
5.5 派生类的构造函数和析构函数 构造函数的主要作用是对数据成员初始化。在第三章我们介绍过,用户在声明类时可以不定义构造函数,系统会自动设置一个默认的构造函数,在定义类对象时会自动调用这个默认的构造函数。这个默认的构造函数实际上是个空函数,不执行任何操作,如果需要初始化对象的数据成员,应该自己定义构造函数。 我们已经知道,基类的构造函数是不能继承的。因此,对继承来的基类成员初始化的工作,要由派生类的构造函数承担。所以,在设计派生类的构造函数时,不仅要考虑初始化派生类自己新增的数据成员,还要考虑初始化基类的数据成员。 解决问题的思路是:在执行派生类的构造函数时,调用基类的构造函数。
5.5 派生类的构造函数和析构函数 • 简单派生类的构造函数 我们说的“简单”是指,一个基类直接派生出一个子类,派生类的数据成员中不包含基类的对象(即子类)。 • 简单派生类的构造函数一般形式 派生类构造函数名 (总参数表列) : 基类构造函数名(参数表列) { 派生类中新增数据成员初始化语句 } 下面以一个派生类的实例来说明有些什么东西。
5.5 派生类的构造函数和析构函数 #include <iostream.h> #include <string.h> class person // 基类声明 {public: person (int i, string nam) //基类构造函数 { id = i; name = nam; } protected: int id; string name; }; class student : public person // 声明公用派生类 { public: student( int i, string nam, int sid) : person( i, nam) { stuid = sid; } // 子类构造函数 void show( ) {cout<<“id:”<<id<<endl; cout<<“name:”<<name<<endl; cout<<“stuid:”<<stuid<<endl; } private: int stuid; }; void main( ) { student s1(1234,”张三”,200701); s1.show( ); }
5.5 派生类的构造函数和析构函数 将派生类的构造函数形式与第三章介绍的“用参数初始化表对数据成员初始化”的介绍,对照看看: student( int i, string nam, int sid) : person( i, nam) { stuid = sid; } box::box(int h, int w, int len): height (h), width (w), length (len) { } 它也有一个冒号,在冒号后面是对数据成员的初始化表。很相似吧? 实际上,本章介绍的在派生类构造函数中对基类成员初始化,就是第三章介绍的构造函数初始化表。也就是说: • 不仅可以用初始化表对数据成员初始化, • 而且可以利用初始化表调用派生类的基类构造函数,实现对基类数据成员的初始化。 • 也可以在同一个构造函数中同时实现这两种功能。
5.5 派生类的构造函数和析构函数 例如,我们可以将上一个示例的子类构造函数改写成这样: student( int i, string nam, int sid) : person( i, nam),stuid(sid) { } 这样,函数体为空,显得很简捷,方便。 • 在建立一个对象时,执行构造函数的顺序是: • 先调用基类构造函数; • 再执行派生类自身的构造函数。 对于刚才的示例,先初始化 id,name,再初始化 stuid。
5.5 派生类的构造函数和析构函数 • 有子对象的派生类的构造函数 如果类的数据成员除了标准数据类型外,还包含类对象,这种被包含的对象就叫“子对象( subobject ),即对象中的对象。 我们仍以学生类为示例。在person的派生类student中,出来增加学号stuid,还增加“家长parent”一项,即学生数据中包含他的家长姓名等相关情况,而家长本身也属于 person 类型,这样,家长parent 项既是基类 person 对象,也派生类student 的子对象。 那么,初始化子类对象的数据成员时,这样对它所包含的子对象初始化呢?这是我们所关心的问题。请看下面程序:
5.5 派生类的构造函数和析构函数 #include <iostream.h> #include <string.h> class person {public: person (int i, string nam) { id = i; name = nam; } void display( ) { cout<<“id:”<<id<<endl; cout<<“name:”<<name<<endl; } protected: int id; string name; }; class student : public person { public: student( int i, string nam, int pid, string pnam, int sid) : person( i, nam),parent(pid,pnam) { stuid = sid; } 派生类构造函数任务:1,初始化基类数据成员;2,初始化子对象数据成员;3,初始化派生类新增数据成员。 void show( ) {cout<<“id:”<<id<<endl; cout<<“name:”<<name<<endl; cout<<“stuid:”<<stuid<<endl; } void showParent( ) {cout<<“parent_id:”<<endl; parent.display( ); } private: int stuid; person parent; }; void main( ) { student s1(1234,”张三”, 2211,”张父”,200701); s1.show( ); s1.showParent( ); }
5.5 派生类的构造函数和析构函数 归纳起来,我们有 • 派生类构造函数的一般形式: 派生类构造函数名(总参数表): 基类构造函数名(参数表列), 子对象名(参数表列), { 派生类中新增数据数据成员初始化语句 } • 执行派生类构造函数的顺序: • 调用基类构造函数,初始化基类数据成员; • 调用子对象构造函数,初始化子对象数据成员; • 再执行派生类自身的构造函数,初始化自己新增数据成员。
5.5 派生类的构造函数和析构函数 上例中的 student 派生类构造函数可写成如下形式: student( int i, string nam, int pid, string pnam, int sid ) : person ( i, nam), parent (pid,pnam), stuid ( sid ) { } 可以看到,函数体为空。此时,派生类构造函数参数个数等于基类构造函数参数、子对象构造函数参数、自己新增数据对象初始化参数之和。这种形式十分常见。
5.5 派生类的构造函数和析构函数 • 多层派生时的构造函数 当出现多层派生时,不能列出每一层的构造函数,只须写出其上一层派生类(即它的直接基类)的构造函数就可以了。 #include <iostreaam.h> #include <string.h> class student {public: student (int n, string nam) { num = n; name = nam; } void display( ) {cout<<“num:”<<num<<endl; cout<<“name:”<<name<<endl; } private: int num; string name; }; class student1 : public student {public: student1 (int n, string nam, int a): student (n, nam) { age = =a; } void show( ) {display ( ); cout<<“age:”<<age<<endl; } private: int age; }; class student2 : public student1 {public: student2 (int n, string nam, int a,int s): student1 (n, nam,a) { score = s; } void show_all( ) { show( ); cout<<“age:”<<age<<endl; } private: int score; }; void main( ) { student2 stud(1001,”Li”,17,90); stud.show_all( ); }
5.5 派生类的构造函数和析构函数 程序中,基类student 构造函数首部为: student ( int n, string nam ) 一级派生类student1 构造函数首部为 student1 ( int n, string nam, int a ):student (n,nam) 二级派生类student2 构造函数首部为 student2 ( int n, string nam, int a, int s): student1 (n,nam,a) 注意,二级派生类student2 构造函数首部不要写成: student2 ( int n, string nam, int a, int s): student (n, nam), student1(n, m, a) //错误!! 不要列出每一层构造函数,只须列出其上一层的构造函数。
5.5 派生类的构造函数和析构函数 • 派生类的析构函数 • 析构函数的作用:我们已经学过,析构函数的作用是在撤消对象之前,给编程人员一个机会,来进行一些清理工作。当对象被删除时,系统会自动调用析构函数。 和构造函数一样,析构函数也是不能派生给派生类的,在派生类中必须编写自己的析构函数,来作些派生类自己的清理工作。 • 调用析构函数的顺序:与调用构造函数的顺序正好相反: • 先执行派生类自己的析构函数,清理派生自己新增数据成员; • 再调用子对象的析构函数,清理子对象; • 最后调用基类的析构函数,对基类进行清理。
5.6 多重继承 • 什么叫多重继承?一个子类由多个基类派生而来,叫多重继承。比如所,在职研究生,既有在职职工的属性,又有研究生的属性。C++为了适应这种情况,允许一个派生类同时继承多个基类,这种行为就是多重继承( multiple inheritance )。 • 声明多重继承的方法:设已声明了类A,B,C,可以从它们派生出子类D,其声明的一般形式为: class D : public A, private B, protected C { 类D新增加的成员 } D是按照不同的继承方式从不同的基类多重继承而来的派生类。
5.6 多重继承 • 多重继承派生类的构造函数:多重继承派生类的构造函数与单继承派生类的构造函数基本相同,只是在初始表中包含多个基类构造函数。其一般形式是: 派生类构造函数名(总参数表): 基类1构造函数 (参数列表1), 基类2构造函数 (参数列表2), 基类3构造函数 (参数列表3) { 派生类中新增数据成员初始化语句 } 例:分别声明一个职工类(姓名,工资 )和一个研究生类( 姓名,成绩 ),用多重继承的方式从这两个类派生出在职研究生类( 新增学号 )。在定义派生类时给出初始化数据,然后输出这些数据。
5.6 多重继承 #include <iostream> #include <string> using namespace std; class worker {public: worker (string nam, int sal) { name = nam; salary = sal; } void wdisplay( ) {cout<<“name:”<<wname<<endl; cout<<“salary:”<<salary<<endl; } protected: string wname; int salary; }; class student {public: student(string snam, int s ) { sname = snam; score = s; } void sdisplay( ) {cout<<“name:”<<sname<<endl; cout<<“score:”<<score<<endl; } protected: string sname; int score; }; class graduate : public worker, public student {public: graduate(string nam,int sal,int s,int i ) : worker (nam, sal), student (nam, s), id(i) { } void gdisplay( ) { cout<<“id:”<<id<<endl; cout<<“wname:”<<name<<endl; cout<<“salary:”<<salary<<endl; cout<<“score:”<<score<<endl; } private: int id; }; void main( ) { graduate s1(“Li”, 2000, 90, 1001); s1.gdisplay( ); }
5.6 多重继承 此例程中,请注意: • 两个基类的数据成员都声明成 protected 类型,因此可以通过派生类的成员函数引用这两个基类的数据成员。 • 同学们可能注意到,两个基类中,分别用wname和sname来表示姓名,其实是同一个人的姓名,能否用同一名称呢?不行。因为,这样一来,同一个派生类中就有两个同名的数据成员,引起二义性。 • 多重继承时,从不同的基类中会继承一些重复的数据,例如本例中姓名就是重复的。在实际编程应用中,会有更多的重复数据,问题会更突出。
C类 int a; A类 B类 基类A 的成员 void display( ) int a; int a; void display( ) void display( ) int a; 基类B 的成员 void display( ) int b; 派生类C 新增成员 void show( ) 5.6 多重继承 • 多重继承的二义性 多重继承给编写程序带来灵活性,但也引出了“二义性( ambiguous )”问题。我们来看看。 若类A和类B中都有成员函数display和数据成员a ,类C是A、B的多重继承的直接派生类。我们下列三种情况讨论: • 两个基类有同名成员, 如右图示。假设C 类有一对象c1, 我们写: c1.a =3; c1.display( );
C类 int A::a; 基类A 的成员 void A::display( ) int B::a; 基类B 的成员 void B::display( ) int b; 派生类C 新增成员 void show( ) 5.6 多重继承 由于基类A、B都有数据成员a,和成员函数display( ), 系统没法判断要访问哪一个基类成员。解决的办法是,在成员名前加上对象名来限制: c1.A::a = 3; c1.A::display( ); 如果在派生类C中,通过成员函 数show( ) 来访问基类的display( ) 和 a,可以直接写成: A::a = 3; A::display( );
C类 int A::a; 基类A 的成员 void A::display( ) int B::a; 基类B 的成员 void B::display( ) int a; 派生类C 新增成员 void display( ) 5.6 多重继承 • 两个基类和派生类都有同名成员,则C类的成员函数变为: class C : public A, public B { int a; void display( ); } 在main函数中,定义C对象为c1, 并调用a 和 dispaly( ): C c1; c1.a =3; c1,.display( ); 这访问的是C的成员,因为有规则:基类的同名成员在派生类中被屏蔽。
class N {public: int a; void display( ) { cout<<“N::a:” <<a<<endl; } }; class A : public N {public: int a1; }; class B : public N {public: int a2; }; class C : publci A, public B {public: int a3; void show( ) {cout<<“a3:“ <<a3<<endl; } }; void main( ) { C c1; c1.A::a:=3; c1.A::display( ); } 类N 类A 类B 类C 5.6 多重继承 • 如果类A、B是从同一个基类派生的,如下图示: 基类N的成员a和display( )在一级派生类A、B中都存在。若要访问A类中的a和display( ),必须加上类名限定。
C类 int A::a; int A::a1; int B::a; int B::a2; int a; 基类N的成员 A新增成员 基类N的成员 B新增成员 C新增成语 N 派生类 C的数 据成员 A C N void A:;display( ); void B::display( ); void show( ); B 派生类 C的成 员函数 5.6 多重继承 • 虚基类 如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,那么在最终的派生类中回保留该间接共同基类数据成员的多份同名成员。前一个示例就是这种情况。
5.6 多重继承 在一个类中保留间接共同基类的多份同名成员,虽然有时是必要的,可以在不同的数据成员中存放不同的数据。但大多数情况下是不必要的,浪费较多内存空间,容易出错。实际上并不需要这么多份拷贝。 C++提供虚基类的技术来解决这仪问题。 其解决思路是,在继承间接共同基类时,内存里只存储一份成员。 • 虚基类定义的一般形式 class 派生类名:virtual 继承方式 基类名 即在声明派生类时,将关键字 virtual 加在继承方式前面。经过这样的声明后,当基类通过多条派生路径被一个派生类中只继承一次,即基类成员只保留一次。
类N virtual virtual 类A 类C 类B 类D 5.6 多重继承 需要注意的是,为了保证虚 基类在派生类中只继承一次,应该 在该基类的所有直接派生类中声明 为虚基类。否则仍然会出现对虚基 类的多次继承。 如果象右图那样,只在A,B 中将N声明为虚基类,而C中没有 将N声明为虚基类,则派生类D 中仍然有冗余的基类拷贝。
5.6 多重继承 • 虚基类的初始化:在最后的派生类中,不仅要初始化直接基类,而且还要初始化虚基类。 如果虚基类中定义了带参数的构造函数,而且没有定义默认构造函数,则在所有直接派生和间接派生类中通过构造函数的初始化表对虚基类进行初始化。如: 注意,在定义多重继承的 派生类D时,由于虚基类在派生 类中只有一份数据成员,所以 这份数据成员的初始化必须由 最后的派生类直接给出,以避免 出现B、C的构造函数给出不同 的初始化参数而产生矛盾。 class A { A (int i) { } }; class B : virtual public A { B (int n) : A(n) { } }; class C: virtual public A { C(int n) : A(n) { } }; class D: public B, public C { D(int n): A(n), B(n), C(n) { } };
5.6 多重继承 #include <iostream> #include <string> Using namespace std; class person {public: person (string nam, char s, int a) { name = nam; sex = s; age = a; } protected: string name; char sex; int age; }; class teacher : virtual public person {public: teacher (string nam, char s, int a, string t) : person(nam,s,a) { title = t; } protected: string title; }; class student : virtual public person {public: student (string nam, char s, int a, float sco) : person(nam,s,a),score(sco) { } protected: float sco; }; class graduate: public teacher, public student {public: graduate (string nam, char s,int a, string t, float sco, float w): person (nam,s,a), teacher (nam,s,a,t), student (nam,s,a,sco),wage(w) { } void show( ) {cout<<“name:”<<name<<endl; cout<<“age:”<<age<<endl; cout<<“sex:”<<sex<<endl; cout<<“score:”<<score<<endl; cout<<“wages:”<<wage<<endl; } private: float wage; }; void main( ) { graduate s1(“wan”,”m”,24,”assistant”,90.5,2000.50); s1.show( ); }
5.7 基类与派生类的转换 不同类型数据之间在一定条件下可以进行类型转换,如,整数赋给双精度数,但不能赋给指针变量。这种不同类型数据之间的自动转换和赋值,称为“赋值兼容”。 我们现在关心的是,基类和派生类之间是否也有赋值兼容的关系,能否进行类型间的转换? 回答是肯定的。基类与派生类之间有赋值兼容关系,由于派生类中包含从基类继承的成员,因此可以将派生类的值赋给基类对象,在用到基类对象的时候可以用其子类对象代替。我们分以下几种情况来讨论。