C++面试之继承和多态

发布于:2024-10-13 ⋅ 阅读:(54) ⋅ 点赞:(0)

(倒反天罡) 概念选择题答案(0411-0422)

1 C        2 A        3 B        4 B        5 D        6 D        7 D        8 C        9 D        10 D        11 C

12 E        13 C

继承有什么作用?

继承可以重写或重定义父类中的一些方法,这在一定程度上破坏了父类的 "封装性",注意这里所谓的破坏指的只是创建子类对象后,父类的一些封装好的方法将发生变化。

继承和组合有什么区别?

继承和组合的区别见下表

继承 组合
父类实现的方法对于子类来说是可见的 父类实现的方法对于子类来说是不可见的
子类中对父类方法的复用属于静态复用(白箱复用) 子类中对父类方法的复用属于动态复用(黑箱复用)

由于在继承中,子类高度依赖父类,所以在设计类与类之间的关系时,更多使用的是组合

好了,看了上面的内容,做一下下面这道题吧

(C++ 1)面向对象设计中的继承和组合,下面说法错误的是?()

A :继承允许我们覆盖重写父类的实现细节,父类的实现对于子类是可见的,是一种静态复
用,也称为白盒复用
B :组合的对象不需要关心各自的实现细节,之间的关系是在运行时候才确定的,是一种动
态复用,也称为黑盒复用
C :优先使用继承,而不是组合,是面向对象设计的第二原则
D :继承可以使子类能自动继承父类的接口,但在设计模式中认为这是一种破坏了父类的封
装性的表现
抽象类的存在就是为了在抽象类中划分出更多具体的子类
一个类是抽象类  等价于  这个类中设计了一个纯虚函数

什么是纯虚函数?

纯虚函数最显著的特征:

        它们必须在继承类中重新声明函数(不要后面的=0,否则该派生类也不能实例化),而且它们在抽象类中往往没有定义。这样就声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例。(参考:https://zhuanlan.zhihu.com/p/270614081)

好了,看了上面的内容,做一下下面这道题吧

(C++ 2)以下关于纯虚函数的说法, 正确的是 ( )
A :声明纯虚函数的类不能实例化对象 B :声明纯虚函数的类是虚基类
C :子类必须实现基类的纯虚函数 D :纯虚函数必须是空函数

        

很多虚函数的调用必须通过指针或者引用来完成,

这是在运行阶段才能确定具体的调用行为,这导致了:

        第一,inline是在编译阶段就把调用处的代码替换成被调用的的函数实体,但是,那么此时调用处的代码无法被替换成虚函数的函数实体,所以虚函数不能设计成内联函数。

        第二,虚函数不能设计成静态函数

好了,看了上面的内容,做一下下面这道题吧

(C++ 3)关于虚函数的描述正确的是( )
A :派生类的虚函数与基类的虚函数具有不同的参数个数和类型 B :内联函数不能是虚函数
C :派生类必须重新定义基类的虚函数 D :虚函数可以是一个 static 型的函数

子类中从父类继承下来的那一部分和父类是不同的

C++可以多继承,多继承表名一个子类可以从多个类中继承数据和方法,

下面讨论如下场景:

        一个类从两个父类中进行继承,且这两个父类分别都有一个虚函数,记父类的名称分别为α,β,在子类中也定义了一个新的虚函数

那么这个子类中有三个部分:

        第一部分包括:α对应的数据和方法 和 α对应的虚函数表指针

        第二部分包括:β对应的数据和方法 和 β对应的虚函数表指针

        第三部分包括:专属于子类的数据和方法

虚函数表指针要等到该类在创建实例时执行初始化列表才能确定,这种行为也称为动态绑定

但是虚函数表是在编译阶段就已经由类的设计确定了的,虚函数表本质是一个函数指针数组

每个类都在编译期间分配了一个专属于这个类的函数指针数组,如果用这个类创建了多个对象,这些对象对应的函数指针数组是相同的

第一部分的虚表指针指向的虚表(也就是函数指针数组)中有两个元素

        第一个函数指针:指向α中定义的虚函数,

        第二个函数指针:指向子类中定义的新的虚函数

第二部分的虚表指针指向的虚表中有只有一个函数指针,指向α中定义的虚函数

好了,看了上面的内容,做一下下面这两道题吧

(C++ 4)假设D先继承B1,然后继承B2,B1和B2基类均包含虚函数,D类对B1和B2基类的 虚函数重写了,并且D类增加了新的虚函数,则()
A.D类对象模型中包含了3个虚表指针
B.D类对象有两个虚表,D类新增加的虚函数放在第一张虚表最后
C.D类对象有两个虚表,D类新增加的虚函数放在第二张虚表最后
D.以上全部错误
(C++ 5)关于虚表说法正确的是( )
A :一个类只能有一张虚表
B :基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表
C :虚表是在运行期间动态生成的
D :一个类的不同对象共享该类的虚表

虚函数表和虚函数表指针

虚函数表是一个函数指针数组,存放在常量区(或称代码段)中,

只要一个类或者其父类有虚函数存在,则一定存在至少一个虚函数表指针

虚函数表指针与类的位置关系,虚函数表指针是类实例化对象后最先开辟空间的成员变量

父类和子类虚函数表指针的关系

下面讨论如下场景,注意:下面的讨论和虚函数是否构成重写无关

        父类α中已经存在一个虚函数f, 且父类中没有继承其他任何的类,子类α1在类中重写了f,而且α1中不存在其他虚函数

父类创建的实例会存放虚表指针p1 和 父类所有的成员变量

子类创建的实例会分为两部分内存空间存放成员变量

        第一部分:虚表指针p2 和 父类所有的成员变量

        第二部分:子类所有成员变量

在这一场景中,p1和p2不同,子类和父类是两个不同的类,p1 != p2

好了,看了上面的内容,做一下下面这道题吧

(C++ 6)假设A 类中有虚函数, B 继承自 A B 重写 A中的虚函数,也没有定义任何虚函数,则( )
A A 类对象的前 4 个字节存储虚表地址, B 类对象前 4个字节不是虚表地址
B A 类对象和 B 类对象前 4个字节存储的都是虚基表的地址
C A 类对象和 B 类对象前 4个字节存储的虚表地址相同
D A 类和 B 类虚表中虚函数个数相同,但 A 类和 B类使用的不是同一张虚表

抽象类不能定义实例,但是可以有指向抽象类的指针

is-a 和 has-a

在继承中,子类是一个特殊的父类。在组合中,两个类的关系更可能是一种包含的关系

        比如,奔驰是一种汽车,此时奔驰类和汽车类的关系被称作 is-a 。

        再比如,汽车都有轮胎,此时汽车类和轮胎类的关系被称作 has-a。

在一个类的一开始加上final,可以使得这个类不能被继承

好了,看了上面的内容,做一下下面这道题吧

(C++ 7)关于继承说法正确的是 ( )
A :所有的类都可以被继承
B :Car(汽车)类和Tire(轮胎)类可以使用继承方式体现
C :继承是实现代码复用的唯一手段
D :狗是一种动物,可以体现出继承的思想

继承权限

class 关键字定义的类,如果有其他子类进行继承,可以不写继承方式,默认为 private 继承

struct 关键字定义的类,如果有其他子类进行继承,可以不写继承方式,默认为 public 继承

子类在继承了父类的私有成员之后,如果创建一个子类对象,子类对象存储的内存空间中会给这些父类中的私有成员专门开辟空间来存储,但是子类对象没有访问权限

好了,看了上面的内容,做一下下面这道题吧

(C++ 8) 下面关于继承权限说法正确的是 ( )
A:派生类在继承基类时,必须明确指定继承方式
B :class 定义的类,默认的访问权限是protected
C :struct 定义的类,默认的访问权限是public
D :子类没有继承基类私有的成员

重定义和函数重载

首先,在一个作用域中,不能定义两个相同名称的变量,也不能定义两个原型完全相同的函数,函数原型相同是指:两个函数的函数名和参数类型完全相同

子类继承了父类之后,子类的类域被划分成了两个作用域,两个作用域之间互不干扰

互不干扰是指可以有以下情况发生

        1. 两个作用域中分别定义两个变量,这两个变量的名称和类型相同,假设此变量名为 v

        2. 两个作用域中分别定义两个函数,这两个函数的名称完全相同,假设此函数名为 f

这两种情况有一种发生,都发生了重定义,注意2. 中对于参数列表和返回值的异同情况不作关注

此时创建一个子类对象,

        当子类对象访问 v 时,会默认访问子类新的作用域中的那个 v

        当子类对象调用 f 时,会默认访问子类新的作用域中的那个 f

如果想要子类旧的作用域中的 v 和 f,只需要在调用时在前面加上 父类名 ::

在一个作用域中,如果出现了两个同名函数,而且这两个同名函数的参数列表不同,那么此时发生了函数重载,注意此时对于函数返回值的一同情况不作关注

此时调用这个作用域中的函数,要根据函数调用时,参数列表的匹配情况确定调用哪个函数

好了,看了上面的内容,做一下下面这道题吧

(C++ 9) 关于同名隐藏的说法正确的是 ( )
A:同一个类中,不能存在相同名称的成员函数
B :在基类和子类中,可以存在相同名称但参数列表不同的函数,他们形成重载
C :在基类和子类中,不能存在函数原型完全相同的函数,因为编译时会报错
D :成员函数可以同名,只要参数类型不同即可,成员变量不能同名,即使类型不同

子类和父类中构造函数和析构函数的关系

针对子类对象在创建时,以及生命周期到了以后结束时这两个阶段分别进行讨论

子类对象在创建时,分两种情况

情况一:子类的初始化列表中没有关于父类构造的代码

        此时先调用父类的 默认 构造函数,再调用子类的构造函数

情况二:子类的初始化列表中存在关于创建父类对象的代码

        此时先调用父类的构造函数,再调用子类的构造函数

注意:此时调用父类的构造函数一定是通过父类的指针进行调用的

子类对象在销毁时,

此时先调用子类中的析构函数,再调用父类中的析构函数

注意:父类中的析构函数最好设计成虚函数,保证子类对象在销毁时,不会造成内存泄漏的问题

好了,看了上面的内容,做一下下面这道题吧

(C++ 10) 下面说法正确的是 ( )
A:派生类构造函数初始化列表的位置必须显式调用基类的构造函数,以完成基类部分成员的初始化
B :派生类构造函数先初始化子类成员,再初始化基类成员
C :派生类析构函数不会自动析构基类部分成员
D :子类构造函数的定义有时需要参考基类构造函数

静态成员的继承结果

静态成员函数也会被继承

静态成员变量在整个继承体系中只有一份,不属于任何对象,也就是说,在类实例化之后,调用sizeof(该类),结果不会考虑静态成员变量的大小

好了,看了上面的内容,做一下下面这两道题吧

(C++ 11) 下列哪个说法不正确 ( )
A:静态成员函数可以被继承
B :所有成员变量都可以被继承
C :基类的友元函数可以被继承
D :静态成员变量在整个继承体系中只有一份
(C++ 12) 关于基类与派生类对象模型说法正确的是 ( )
A:基类对象中包含了所有基类的成员变量
B :子类对象中不仅包含了所有基类成员变量,也包含了所有子类成员变量
C :子类对象中没有包含基类的私有成员
D :基类的静态成员可以不包含在子类对象中
E:   以上说法都不对

菱形继承需要注意的事项

菱形继承讨论的是如下场景:

class B {
public: 
    int b;
};

class C1: public B {
public: 
    int c1;
};

class C2: public B {
public: 
    int c2;
};

class D : public C1, public C2 {
public:
    int d;
};

此时D类对象开辟空间的示意图如下

此时由于有两个 b,造成了数据冗余和二义性的问题,要访问其中的一个 b,必须通过加上类名来确定使用哪个域中的 b

好了,看了上面的内容,做一下下面这道题吧

(C++ 13) 考虑如下场景,关于菱形继承,说法不正确的是( )

class B {
public: 
    int b;
};

class C1: public B {
public: 
    int c1;
};

class C2: public B {
public: 
    int c2;
};

class D : public C1, public C2 {
public:
    int d;
};

A:D总共占了20个字节
B :B中的内容总共在D对象中存储了两份
C :D对象可以直接访问从基类继承的 b 成员
D :菱形继承存在二义性问题,尽量避免设计菱形继承

---------------------------------------------------------------------------------------------------------------------------

以下是程序结果题

(倒反天罡) 程序结果题答案

一、0  1  2

二、A   B  C  D

三、C

四、B

好了,做一下下面的程序结果题吧

题目一:

题目二:

题目三:
题目四:

网站公告

今日签到

点亮在社区的每一天
去签到