第八章继承与多态
继承(inheritance):
该机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构。体现了由简单到复杂的认识过程。
多态性(polymorphism):
多态性是考虑在不同层次的类中,以及在同一类中,同名的成员函数之间的关系问题。函数的重载,运算符的重载,属于编译时的多态性。以虚函数为基础的运行时的多态性是面向对象程序设计的标志性特征。体现了类推和比喻的思想方法。
第八章继承与多态
8.1 继承与派生的概念8.4 虚基类(选读)
8.3 多重继承与派生类成员标识(选读)8. 6 多态性与虚函数
8.5 派生类应用讨论
8.2 派生类的构造函数与析构函数
8.1继承与派生的概念
层次概念:
层次概念是计算机的重要概念。通过继承(inheritance)的机制可对类(class)分层,提供类型/子类型的关系。
C++通过类派生(class derivation)的机制来支持继承。被继承的类称为基类(base class)或超类(superclass),新的类为派生类(derived class)或子类(subclass)。基
类和派生类的集合称作类继承层次结构(hierarchy)。
如果基类和派生类共享相同的公有接口,则派生类被称作基类的子类型(subtype)。
派生反映了事物之间的联系,事物的共性与个性之间的关系。派生与独立设计若干相关的类,前者工作量少,重复的部分可以从基类继承来,不需要单独编程。
8.1继承与派生的概念
8.1.1 类的派生与继承
8. 1.2 公有派生与私有派生
8.1.1 类的派生与继承
派生类的定义:
class 派生类名:访问限定符基类名1《,访问限定符基类名2,……,访问限定符基类名n》{
《《private: 》
成员表1;》//派生类增加或替代的私有成员《public:
成员表2;》//派生类增加或替代的公有成员《protected:
成员表3;》//派生类增加或替代的保护成员
};//分号不可少
其中基类1,基类2,……是已声明的类。在派生类定义的类体中给出的成员称为派生类成员,它们是新增加成员,它们给派生类添加了不同于基类的新的属性和功能。派生类成员也包括取代基类成员的更新成员。
基类1基类2……基类n 派生类1派生类
2基类派生类1派生类2
(a )多重继承(b )单继承一个基类可以直接派生出多个派生类派生类可
以由多个
基类共同
派生出来,
称多重继
承。8.1.1 类的派生与继承
多重继承:如果一个派生类可以同时有多个基类,称为多重继承(multiple-inheritance ),这时的派生类同时得到了多个已有类的特征。
单继承:派生类只有一个直接基类的情况称为单继承(single-inheritance )。
8.1.1 类的派生与继承
多层次继承:
在派生过程中,派生出来的新类同样可以作为基类再继续派生出更新的类,依此类推形
成一个层次结构。直接参与派生出某类称为直
接基类,而基类的基类,以及更深层的基类称
为间接基类。
类族:
同时一个基类可以直接派生出多个派生类。这样形成了一个相互关联的类族。如MFC就是这样的族类,它由一个CObject类派生出200
个MFC类中的绝大多数。
编制派生类时可分四步
吸收基类的成员
改造基类成员
发展新成员
重写构造函数与析构函数
8.1.1 类的派生与继承
不论是数据成员,还是函数成员,
除构造函数与析构函数外全盘接收
声明一个和某基类成员同名的新成员,派
生类中的新成员就屏蔽了基类同名成员
称为同名覆盖(override)
派生类新成员必须与基类成员不同名,它
的加入保证派生类在功能上有所发展。派生编程步骤:
8.1.1 类的派生与继承
第二步中,新成员如是成员函数,参数表也必须一样,否则是重载。
第三步中,独有的新成员才是继承与派生的核心特征。
第四步是重写构造函数与析构函数,派生类不继承这两种函数。不管原来的函数是否可用一律重写可免出错。
访问控制:
亦称为继承方式,是对基类成员进一步的限制。访问控制也是三种:
公有(public)方式,亦称公有继承
保护(protected)方式,亦称保护继承
私有(private)方式,亦称私有继承。
访问限定符两方面含义:派生类成员(新增成员)函数对基类(继承来的)成员的访问(调用和操作),和从派生类对象之外对派生类对象中的基类成员的访问。
不可直接访问不可直接访问private 不可直接访问private protected
不可直接访问private public
私有派生
不可直接访问不可直接访问private
不可直接访问protected protected
可直接访问public public
公有派生在派生类对象外访问派生类对象的基类成员在派生类中对基类成员的访问限定基类中的
访问限定
派生方式公有派生是绝对主流。
保护派生:
直接派生:基类的私有成员仍是派生类的私有成员,不可直接访问,而基类中的公有和保护成员全部成为派生类的保护成员,在派生类中可以直接访问。但在派生类对象之外是不能直接访问该对象的任何基类成员的。与私有派生相同。
多层派生:把保护派生类作为基类或把私有派生类作为基类再作一层保护派生。在新的保护派生类中可直接访问由保护派生传递过来的底层基类的公有和保护成员,而不可直接访问由私有派生传递来的底层基类的公有和保护成员。但在该类对象之外都不可直接访问类对象底层基类的公有成员。合理使用保护限定方式可以在复杂的类层次关系中取一个共享访问和成员封装隐蔽性的折衷。
8.2 派生类的构造函数与析构函数
派生类构造函数的定义:
派生类名::派生类名(参数总表):基类名1(参数名表1)《,基类名2(参数名表2),……,基类名n(参数名表n)》,《成员对象名1(成员对象参数名表1),……,成员对象名m(成员对象参数名表m)》{
……//派生类新增成员的初始化;
} //所列出的成员对象名全部为新增成员对象的名字
注意:
在构造函数的声明中,冒号及冒号以后部分必须略去。
所谓不能继承并不是不能利用,而是把基类的构造函数作为新的构造函数的一部分,或者讲调用基类的构造函数。基类名仅指直接基类,写了底层基类,编译器认为出错。
冒号后的基类名,成员对象名的次序可以随意,这里的次序与调用次序无关。
8.2 派生类的构造函数与析构函数
派生类构造函数各部分执行次序:
1.调用基类构造函数,按它们在派生类定义
的先后顺序,顺序调用。
2.调用成员对象的构造函数,按它们在类定
义中声明的先后顺序,顺序调用。
3.派生类的构造函数体中的操作。
注意:
在派生类构造函数中,只要基类不是使用无参的默认构造函数都要显式给出基类名和参数表。
如果基类没有定义构造函数,则派生类也可以不定义,全部采用系统给定的默认构造函数。
如果基类定义了带有形参表的构造函数时,派生类就应当定义构造函数。
8.2 派生类的构造函数与析构函数
析构函数:
析构函数的功能是作善后工作。
只要在函数体内把派生类新增的一般成员处理好就可以了,而对新增的成员对象和基类的善后工作,系统会自己调用成员对象和基类的析构函数来完成。
析构函数各部分执行次序与构造函数相反,首先对派生类新增一般成员析构,然后对新增对象成员析构,最后对基类成员析构。
【例8.1】由在册人员类公有派生学生类【例8.1】由在册人员类公有派生学生类。我
们希望基类和派生类共享相同的公有接口,只
能采用公有派生来实现。
基类:
class Person{
string IdPerson;//身份证号,18位数字
string Name;//姓名
Tsex Sex; //性别enum Tsex{mid,man,woman};
int Birthday;//生日,格式1986年8月18日写作19860818
string HomeAddress;//家庭地址
public:
Person(string, string,Tsex,int, string);//构造函数
Person(); //默认的构造函数
//接口函数:
void SetName(string);//修改名字
string GetName(){return Name;}//提取名字
void SetSex(Tsex sex){Sex=sex;} //修改性别
Tsex GetSex(){return Sex;}//提取性别
void SetId(string id){IdPerson=id;}//修改身份证号
string GetId(){return IdPerson;}//提取身份证号
void SetBirth(int birthday){Birthday=birthday;} //修改生日int GetBirth(){return Birthday;} //提取生日void SetHomeAdd(string );//修改住址string GetHomeAdd(){return HomeAddress;}//提取住址void PrintPersonInfo();//输出个人信息};
派生的学生类:
class Student:public Person{ //定义派生的学生类string NoStudent; //学号
course cs[30]; //30门课程与成绩
public :
Student(string id, string name,Tsex sex,int birthday, string homeadd, string nostud);
//注意派生类构造函数声明方式Student();//默认派生类构造函数~Student(); //派生类析构函数SetCourse(string ,int ); //课程设置
int GetCourse(string ); //查找成绩
void PrintStudentInfo(); //打印学生情况};
struct course {string coursename;int grade;};
8.2 派生类的构造函数与析构函数
注意:
本例中标准C++字符串string是作为成员对象使用的
(聚合),动态内存分配的构造和析构被封装起来,使用十分简单。如使用动态生成的C风格字符串,要考虑深复制,
那要复杂得多。
提倡完善的类对象封装,不仅封装数据和对数据的操作,而且封装资源的动态分配与释放,形成一个完备的子系统。在一个有层次结构的类体系中资源的动态分配与释放应封装在成员对象中,如同使用标准的string字符串类那样。
聚合是一种完善的封装。采用成员对象将大大简化层次结构的类体系中资源的动态分配与释放的处理方法,不再出现难度极大的多层次的深复制。
8.3 多重继承与派生类成员标识(选读)多重继承实例:
由多个基类共同派生出新的派生类,这样的继承结构被称为多重继承或多继承(multiple-inheritance)
椅子床
沙发(单继承)躺椅(多重继承)
两用沙发(多重继承)
图8.2椅子,床到两用沙发