C++【04】继承与派生

面向对象程序设计有4个主要特点: 抽象、封装、继承和多态性。
要较好地进行面向对象程序设计,还必须了解面向对象程序设计另外两个重要特征——继承性和多态性。在本章中主要介绍有关继承的知识,在第6章中将介绍多态性。
面向对象技术强调软件的可重用性(software reusability) 。C++语言提供了类的继承机制,解决了软件重用问题。

一、继承与派生的概念

      在C++中可重用性是通过继承(inheritance)这一机制来实现的。继承是C++的一个重要组成部分。
      一个类中包含了若干数据成员和成员函数。在不同的类中,数据成员和成员函数是不相同的。但有时两个类的内容基本相同或有一部分相同。例如P151 Student、Student1类
      利用原来声明的类作为基础,再加上新的内容即可,可以减少重复的工作量。 C++提供的继承机制就是为了解决这个问题。
      一个新类从已有的类那里获得其已有特性,这种现象称为类的继承。通过继承,一个新建子类从已有的父类那里获得父类的特性。从另一角度说,从已有的类(父类)产生一个新的子类,称为类的派生。类的继承是用已有的类来建立专用类的编程技术。派生类继承了基类的所有数据成员和成员函数,并可以对成员作必要的增加或调整。一个基类可以派生出多个派生类,每一个派生类又可以作为基类再派生出新的派生类,因此基类和派生类是相对而言的。
      以上介绍的是最简单的情况: 一个派生类只从一个基类派生,这称为单继承(single inheritance),这种继承关系所形成的层次是一个树形结构。
      一个派生类不仅可以从一个基类派生,也可以从多个基类派生。一个派生类有两个或多个基类的称为多重继承(multiple inheritance),这种继承关系所形成的结构。
关于基类和派生类的关系,可以表述为: 派生类是基类的具体化,而基类则是派生类的抽象。

二、派生类声明方式

假设已经声明了一个基类Student,在此基础上通过单继承建立一个派生类Student1:
class Student1: public Student //声明基类是Student
{public:
void display_1( ) //新增加的成员函数
{cout<<″age: ″<<age<<endl;
cout<<″address: ″<<addr<<endl;}
private:
int age; //新增加的数据成员
string addr; //新增加的数据成员
};
基类名前面有public的称为“公用继承(public inheritance)”。
声明派生类的一般形式为
class 派生类名: [继承方式] 基类名
{
派生类新增加的成员
} ;
继承方式包括: public(公用的),private(私有的)和protected(受保护的),此项是可选的,如果不写此项,则默认为private(私有的)。

三、派生类的构成

      派生类中的成员包括从基类继承过来的成员和自己增加的成员两大部分。在基类中包括数据成员和成员函数(或称数据与方法)两部分,派生类分为两大部分: 一部分是从基类继承来的成员,另一部分是在声明派生类时增加的部分。每一部分均分别包括数据成员和成员函数。
在这里插入图片描述

四、派生类成员的访问属性

      既然派生类中包含基类成员和派生类自己增加的成员,就产生了这两部分成员的关系和访问属性的问题。在建立派生类的时候,并不是简单地把基类的私有成员直接作为派生类的私有成员,把基类的公用成员直接作为派生类的公用成员。实际上,对基类成员和派生类自己增加的成员是按不同的原则处理的。
      前面已提到: 在派生类中,对基类的继承方式可以有public(公用的),private(私有的)和protected(保护的)3种。不同的继承方式决定了基类成员在派生类中的访问属性。
简单地说:
(1) 公用继承(public inheritance)
基类的公用成员和保护成员在派生类中保持原有访问属性,其私有成员仍为基类私有。
(2) 私有继承(private inheritance)
基类的公用成员和保护成员在派生类中成了私有成员。其私有成员仍为基类私有。
(3) 受保护的继承(protected inheritance)
基类的公用成员和保护成员在派生类中成了保护成员,其私有成员仍为基类私有。
保护成员的意思是: 不能被外界引用,但可以被派生类的成员引用,具体的用法将在稍后介绍。
在这里插入图片描述

五、多级派生时的访问属性

      如果有图5.9所示的派生关系: 类A为基类,类B是类A的派生类,类C是类B的派生类,则类C也是类A的派生类。类B称为类A的直接派生类,类C称为类A的间接派生类。类A是类B的直接基类,是类C的间接基类。在多级派生的情况下,各成员的访问属性仍按以上原则确定。
注意:无论哪一种继承方式,在派生类中是不能访问基类的私有成员的,私有成员只能被本类的成员函数所访问,毕竟派生类与基类不是同一个类。
如果在多级派生时都采用公用继承方式,那么直到最后一级派生类都能访问基类的公用成员和保护成员。
      如果采用私有继承方式,经过若干次派生之后,基类的所有的成员已经变成不可访问的了。
      如果采用保护继承方式,在派生类外是无法访问派生类中的任何成员的。而且经过多次派生后,人们很难清楚地记住哪些成员可以访问,哪些成员不能访问,很容易出错。因此,在实际中,常用的是公用继承。

六、派生类的构造函数和析构函数

构造函数

      用户在声明类时可以不定义构造函数,系统会自动设置一个默认的构造函数,在定义类对象时会自动调用这个默认的构造函数。这个构造函数实际上是一个空函数,不执行任何操作。如果需要对类中的数据成员初始化,应自己定义构造函数。
      构造函数的主要作用是对数据成员初始化。在设计派生类的构造函数时,不仅要考虑派生类所增加的数据成员的初始化,还应当考虑基类的数据成员初始化。也就是说,希望在执行派生类的构造函数时,使派生类的数据成员和基类的数据成员同时都被初始化。解决这个问题的思路是: 在执行派生类的构造函数时,调用基类的构造函数。
请注意派生类构造函数首行的写法:
Student1(int n,string nam,char s,int a,string ad):Student(n,nam,s)
其一般形式为
派生类构造函数名(总参数表列): 基类构造函数名(参数表列)
{派生类中新增数据成员初始化语句}
请注意基类和两个派生类的构造函数的写法:
基类的构造函数首部:
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构造函数;在执行Student2构造函数时,先调用Student1构造函数;在执行Student1构造函数时,先调用基类Student构造函数。初始化的顺序是:
① 先初始化基类的数据成员num和name。
② 再初始化Student1的数据成员age。
③ 最后再初始化Student2的数据成员score。

析构函数

      在派生时,派生类是不能继承基类的析构函数的,也需要通过派生类的析构函数去调用基类的析构函数。在派生类中可以根据需要定义自己的析构函数,用来对派生类中所增加的成员进行清理工作。基类的清理工作仍然由基类的析构函数负责。在执行派生类的析构函数时,系统会自动调用基类的析构函数和子对象的析构函数,对基类和子对象进行清理。
      调用的顺序与构造函数正好相反: 先执行派生类自己的析构函数,对派生类新增加的成员进行清理,然后调用子对象的析构函数,对子对象进行清理,最后调用基类的析构函数,对基类进行清理。

七、多重继承

      前面讨论的是单继承,即一个类是从一个基类派生而来的。实际上,常常有这样的情况: 一个派生类有两个或多个基类,派生类从两个或多个基类中继承所需的属性。C++为了适应这种情况,允许一个派生类同时继承多个基类。这种行为称为多重继承(multiple inheritance)。
如果已声明了类A、类B和类C,可以声明多重继承的派生类D:
class D:public A,private B,protected C
{类D新增加的成员}
D是多重继承的派生类,它以公用继承方式继承A类,以私有继承方式继承B类,以保护继承方式继承C类。D按不同的继承方式的规则继承A,B,C的属性,确定各基类的成员在派生类中的访问权限。

多重继承派生类的构造函数

      多重继承派生类的构造函数形式与单继承时的构造函数形式基本相同,只是在初始表中包含多个基类构造函数。
派生类构造函数名(总参数表列): 基类1构造函数(参数表列), 基类2构造函数(参数表列), 基类3构造函数 (参数表列)
{派生类中新增数成员据成员初始化语句}
各基类的排列顺序任意。派生类构造函数的执行顺序同样为: 先调用基类的构造函数,再执行派生类构造函数的函数体。调用基类构造函数的顺序是按照声明派生类时基类出现的顺序。
基类的同名成员在派生类中被屏蔽

八、虚基类

1.虚基类的作用

      从上面的介绍可知: 如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员。如图5.19和图5.20所示。在引用这些同名的成员时,必须在派生类对象名后增加直接基类名,以避免产生二义性,使其惟一地标识一个成员,如c1.A::display( )。
在一个类中保留间接共同基类的多份同名成员,这种现象是人们不希望出现的。
C++提供虚基类(virtual base class)的方法,使得在继承间接共同基类时只保留一份成员。

2.虚基类的初始化

      如果在虚基类中定义了带参数的构造函数,而且没有定义默认构造函数,则在其所有派生类(包括直接派生或间接派生的派生类)中,通过构造函数的初始化表对虚基类进行初始化。例如

class A//定义基类A
 {A(int i){ }                         //基类构造函数,有一个参数
…};
class B :virtual public A           //A作为B的虚基类
 {B(int n):A(n){ }                   //B类构造函数,在初始化表中对虚基类初始化
…};
class C :virtual public A           //A作为C的虚基类
 {C(int n):A(n){ }                   //C类构造函数,在初始化表中对虚基类初始化
…};
class D :public B,public C     //类D的构造函数,在初始化表中对所有基类初始化
 {D(int n):A(n),B(n),C(n){ }
…};

注意: 在定义类D的构造函数时,与以往使用的方法有所不同。以前,派生类的构造函数只需负责对其直接基类初始化,再由其直接基类负责对间接基类初始化。现在为了避免不同直接基类对虚基类的构造函数初始化参数不同,规定: 在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。
C++编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略虚基类的其他派生类(如类B和类C) 对虚基类的构造函数的调用,这就保证了虚基类的数据成员不会被多次初始化。

猜你喜欢

转载自blog.csdn.net/The_Handsome_Sir/article/details/106795520