第六章 《继承和派生》

目录

1.继承方式有哪几种?

2.派生新类一般经历哪几个阶段?

3.派生类对象的内存结构是什么样的?

4.派生类的构造函数要完成哪些工作?

5.派生类构造函数的执行次序是怎样的?

6.在设计派生类构造函数(含拷贝构造函数)时要注意什么?

7.派生类的析构函数的执行次序?

8.类层次中的类模板

9.简述继承的优点和缺点。

10.哪几种情况需要采用成员初始化列表方式进行初始化?

11.请设计一个不能被继承的类。

12.基类对象和派生类对象的使用关系?

13.什么是虚继承?虚基类怎样声明?

14.虚继承中派生类对象的内存结构是什么样的?。。

15.虚继承中派生类构造函数的执行步骤是怎样的?

16.虚继承中派生类析构函数的执行步骤是怎样的?

17.一个空类的大小为1,从一个空类派生的空子类的大小也是1,那么从一个空类虚继承的空子类的大小是多少?

18.派生类构造函数和基类的构造函数有什么关系?派生类构造函数调用基类的构造函数有哪两种方式?

19.何谓虚基类?它有什么作用?如何使用虚基类?


1.继承方式有哪几种?

public:表示公有继承,此时基类public和protected成员的访问属性在派生类中保持原来的访问属性不变,而基类private成员在派生类中不可访问。即基类的public和protected成员分别作为派生类的public和protected成员,派生类的其他成员可以直接访问它们。

private:表示私有继承(默认的继承方式),此时基类public和protected成员都以private权限出现在派生类中,而基类private成员在派生类中不可访问。即基类的public和protected成员被继承后作为派生类的私有成员,派生类的其他成员可以直接访问它们,但是在类外部通过派生类的对象无法访问它们。无论是派生类的成员还是派生类的对象都无法访问从基类继承的private成员。

protected:表示保护继承,此时基类public和protected成员都以protected权限出现在派生类中,而基类private成员不可访问。即基类的public和protected成员被继承以后作为派生类的保护成员,这样,派生类的其他成员就可以直接访问它们,但在类外通过派生类的对象无法访问。无论是派生类的成员还是派生类的对象都无法访问从基类继承的private成员。


2.派生新类一般经历哪几个阶段?

  1. 吸收基类成员:在C++的类继承首先将基类的成员全部接收,这样派生类实际上就包含了它的基类中除构造函数和析构函数之外的所有成员(注意是全部继承,但有访问权限的限制,例如派生类中不能访问基类的私有成员)。
  2. 改造基类成员:对基类成员的隐藏,即在派生类中定义一个和基类成员同名的成员,由于作用域不同,于是发生同名隐藏,基类中的相应成员被替换成派生类中的同名成员。对于成员函数,无论参数列表是否相同,只要函数名相同,都会发生隐藏。
  3. 添加新的成员:根据实际需要给派生类添加适当的数据成员和成员函数,从而实现必要的新增功能。

对于private或者protected继承方式,基类的公有成员在派生类中变成私有或者保护的,可以在派生类中采用访问声明方式将其恢复成公有成员。

访问声明的一般格式为:

基类名::成员名;   //成员函数名后不要加上()

注意访问声明仅能将继承的成员恢复到原来的访问权限,例如,如果原来在基类中成员是公有的,被继承方式protected或private屏蔽后只能用访问声明恢复为公有的,而不能改为保护或私有的。


3.派生类对象的内存结构是什么样的?

从前面的介绍可知C++类中有四种成员,即静态数据成员、非静态数据成员、静态函数和非静态函数。

非静态数据成员放在每个对象内部,作为对象专有的数据成员,而静态数据成员被抽取出来放在程序的静态数据区内,为该类的所有对象共享,只保留一份,非静态成员函数和静态成员函数最终都被提取出来放在程序的代码段中并为该类的所有对象共享,因此每个成员函数只能存在一份代码实体。

那么,类继承中派生类对象的内存结构是什么样的呢?

  1. 派生类的对象中存放所有基类的非静态数据成员,包括基类的私有数据成员,只是在派生类中不能访问基类的私有成员。
  2. 在派生类对象中各数据成员是按继承方式列表中指定的类顺序排列的。

4.派生类的构造函数要完成哪些工作?

当从基类派生子类时基类的构造函数不能够继承到派生类中,因此派生类的构造函数必须通过调用基类构造函数来完成初始化工作,所以在定义派生类的构造函数时除了对自己的数据成员进行初始化外,还必须负责调用基类构造函数使基类的数据成员得以初始化。如果派生类中还有子对象,还应包含对子对象初始化,这些都包含在成员初始化列表中。

实际上,如果派生类构造函数的成员初始化列表中没有显式列出对基类构造函数的调用,也自动包含对基类默认构造函数的调用。


5.派生类构造函数的执行次序是怎样的?

  1. 调用基类的构造函数:当有多个基类时按派生类定义的次序调用,而不是按初始化列表中的次序。
  2. 调用派生类中子对象(如果有子对象)的构造函数,按子对象创建的顺序调用,而不是按照初始化表中的次序。
  3. 执行函数体。

6.在设计派生类构造函数(含拷贝构造函数)时要注意什么?

  1. 在调用派生类构造函数时总是会调用基类的构造函数,通常在成员初始化列表中给出其调用方式。
  2. 有时派生类构造函数的成员初始化列表中省略对基类构造函数的调用,其条件是在基类中必须有默认的构造函数或者根本没有构造函数(系统自动创建)。
  3. 当基类的重载构造函数使用一个或多个参数时(即基类重载了带参构造函数,且没有无参构造函数时)派生类必须定义构造函数,并通过成员初始化列表提供将参数传递给基类构造函数的途径。
  4. 若派生类中包含类A的子对象a,其定义方式只能是A a;即使该对象需要为其数据成员x赋值10,也不能写成A a(10);,只能在类C的构造函数成员初始化列表中添加a(10)来实现数据成员的初始化。

7.派生类的析构函数的执行次序?

和构造函数一样,析构函数也不能被继承,因此在执行派生类的析构函数时基类的析构函数也将被调用,其顺序与执行构造函数的顺序正好相反。

派生类析构函数的一般执行次序为:

  1. 执行派生类的析构函数;
  2. 调用子对象的析构函数:按类声明中对象成员出现的逆序调用,而不是按初始化列表中的次序。
  3. 调用基类的析构函数:多个基类则按派生类继承声明时列出的逆序从右到左调用,而不是按成员初始化列表中的次序。

8.类层次中的类模板

和类一样,类模板之间也可以存在继承关系,即可以从类模板派生其他类模板。


9.简述继承的优点和缺点。

继承的优点如下:

  1. 类继承是在编译时静态定义的,且可直接使用。
  2. 类继承可以提高代码的重用性。
  3. 类继承可以较方便地改变父类的实现。

继承的缺点:

  1. 继承在编译时就定义了,所以无法在运行时改变从父类继承的实现。
  2. 父类通常至少定义了子类的部分行为,父类的任何改变都可能影响子类的行为。
  3. 如果继承下来的实现不适合解决新的问题,则子类必须重写或被其他更合适的类替换,这种依赖关系限制了灵活性并最终限制了复用性。

10.哪几种情况需要采用成员初始化列表方式进行初始化?

  1. 带有const修饰的常数据成员,例如const int x
  2. 带引用的数据成员,例如int &x
  3. 调用基类构造函数使基类的数据成员得以初始化
  4. 如果派生类中有子对象,还应包含对子对象初始化

11.请设计一个不能被继承的类。

要想让一个类不能被继承,只需要将其构造函数设计为私有的。但此时不能创建该类的任何对象,因为这样的类是没有意义的。为此设计两个静态公有成员函数分别创建该类的实例指针和释放该实例。

对应的类如下:

class A
{
public:
    static A *GetInstance(){return new A;}
    static void DeleteInstance(A *pInstance)
    {
        delete pInstance;
        pInstance=NULL;
    }
private:
    A(){}    //私有构造函数
    ~A(){}   //私有析构函数
};

可以采用以下代码创建类A的实例指针和释放该实例:

A *pa=A::GetInstance();
A::DeleteInstance(pa);

12.基类对象和派生类对象的使用关系?

派生类对象能作为基类对象处理

由于派生类具有基类的所有成员,所以把派生类的对象赋给基类对象是合理的,不过要求这种继承方式必须是public方式。反过来赋值是不允许的,因为派生类的成员通常比基类的成员多。

基类指针能指向派生类对象

因为派生类继承自基类,所以派生类对象的指针可以转换为指向基类的指针,这种向上的引用方式是安全的,称为向上映射。

但是这种方式只能引用基类的成员,如果试图通过基类指针访问那些只有在派生类中才有的成员,编译系统会报告错误。

派生类指针强制指向基类对象

用户可以用基类指针指向派生类对象,那么反过来可不可以呢?

结论是一般情况下不可以,但有两种情况例外。一是采用静态转换运算符static_cast进行强制转换,二是除非基类中包含有虚函数。


13.什么是虚继承?虚基类怎样声明?

在复杂的类层次中,可能存在重复继承的问题,即一个派生类多次继承同一个基类,这种情况下基类的多个相同成员如何区分呢?

解决的办法有两种:

一是使用作用域分辨符来唯一标识并分别访问它们;二是将多次重复继承的基类设置成虚基类,这就是虚继承。

虚基类是当基类被继承时在基类的继承方式前加上关键字virtual,即虚基类的声明格式为:virtual 继承方式 基类名

当某类的部分或全部直接基类是从另一个共同基类派生而来时,这些直接基类中从上一级基类继承来的成员就拥有相同的名字,这时可以将直接基类的共同基类设置为虚基类,从不同路径继承过来的该类成员在内存中只拥有一个副本,从而解决了同名成员的唯一标识问题。


14.虚继承中派生类对象的内存结构是什么样的?。。

这个问题有点不太好说清楚

和普通继承不同的是,派生类对象还需要表示虚继承关系。

当存在虚继承时(不考虑虚函数的情况),派生类对象内存结构的说明如下:

  1. 采用一个虚基类表(vbtable)来存放派生类的所有虚基类,虚基类表里面每一项的单位是4个字节,第一项保存的是虚基类表指针在派生类对象中的相对地址,第二项保存的是第一个虚基类数据成员在派生类对象中的相对地址,第三项保存的是第2个虚基类数据成员在派生类对象中的相对地址,以此类推。简单来讲就是虚基类表中保存了派生类所有虚基类的地址,所以,无论是单虚继承还是多虚继承,都只需要一个虚基类表。在派生类对象中增加指向该虚基类表的指针vbptr,其大小为4个字节,称为虚基类表指针。
  2. 在派生类对象中最开头存放虚基类表指针(并非在任何情况下都是放在最开头),然后存放自己的数据成员,最后存放虚基类的数据成员。在多虚继承时,按继承方式列表中的顺序存放各虚基类的数据成员。

15.虚继承中派生类构造函数的执行步骤是怎样的?

如果派生类有一个虚基类,那么在派生类构造函数的成员初始化列表中需要列出对虚基类构造函数的调用,如果未列出则表明调用的是虚基类的默认无参数的构造函数。

派生类构造函数的执行步骤如下:

  1. 调用基类的构造函数,多个基类则按派生类继承列表中列出的次序(即从左到右)调用,而不是按成员初始化列表中的次序。
  2. 调用子对象成员的构造函数(如果有子对象成员),按类创建对象成员的次序调用,而不是按成员初始化列表中的次序。
  3. 执行派生类的构造函数。

在以上构造函数的调用过程中,同一层中对虚基类构造函数的调用总是先于普通基类的构造函数。当同一层中有多个虚基类时,按继承列表中给出的先后次序调用虚基类的构造函数。


16.虚继承中派生类析构函数的执行步骤是怎样的?

同样,如果存在虚基类,派生类析构函数的执行会涉及其基类或虚基类的析构函数的执行。析构函数的调用次序与构造函数的调用正好相反。

派生类析构函数的执行步骤如下:

  1. 执行派生类的析构函数。
  2. 调用子对象的析构函数(若存在子对象),按类声明中创建子对象的逆序调用,而不是按成员初始化列表中的逆序。
  3. 调用普通基类的析构函数,多个基类则按派生类继承列表中列出次序的逆序(即从右到左)调用,而不是按成员初始化列表中的次序。

在以上析构函数调用过程中,同一层中对普通基类析构函数的调用总是先于虚基类的析构函数。


17.一个空类的大小为1,从一个空类派生的空子类的大小也是1,那么从一个空类虚继承的空子类的大小是多少?

从一个空类虚继承的空子类的大小是4,因为其中需要增加一个虚基类表指针表示虚继承关系。


18.派生类构造函数和基类的构造函数有什么关系?派生类构造函数调用基类的构造函数有哪两种方式?

在调用派生类的构造函数创建派生类对象时,系统首先调用基类的构造函数创建其基类对象。

派生类构造函数调用基类的构造函数有以下两种方式:

  1. 隐式调用:指在派生类的构造函数中不指定对应的基类的构造函数,这时调用的是基类的默认构造函数(即含有默认参数值或不带参数的构造函数)。
  2. 显式调用:指在派生类的构造函数中指定要调用的基类的构造函数,并将派生类构造函数的部分参数传递给基类的构造函数。

19.何谓虚基类?它有什么作用?如何使用虚基类?

虚基类是一种继承方式,如果采用虚基类方式定义派生类,则在创建派生类的对象时类层次结构中的某个虚基类的成员只保留一个,即虚基类的一个副本被所有派生类对象共享。

采用虚基类方式定义派生类的方法是在指定的基类名的前面加上关键字virtual,而定义基类时不需要使用关键字virtual。


参考:《直击招聘 程序员面试笔试 C++语言深度解析》李春葆、李筱池 主编

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值