(倒反天罡) 概念选择题答案(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)面向对象设计中的继承和组合,下面说法错误的是?()
什么是纯虚函数?
纯虚函数最显著的特征:
它们必须在继承类中重新声明函数(不要后面的=0,否则该派生类也不能实例化),而且它们在抽象类中往往没有定义。这样就声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例。(参考:https://zhuanlan.zhihu.com/p/270614081)
好了,看了上面的内容,做一下下面这道题吧
很多虚函数的调用必须通过指针或者引用来完成,
这是在运行阶段才能确定具体的调用行为,这导致了:
第一,inline是在编译阶段就把调用处的代码替换成被调用的的函数实体,但是,那么此时调用处的代码无法被替换成虚函数的函数实体,所以虚函数不能设计成内联函数。
第二,虚函数不能设计成静态函数
好了,看了上面的内容,做一下下面这道题吧
子类中从父类继承下来的那一部分和父类是不同的
C++可以多继承,多继承表名一个子类可以从多个类中继承数据和方法,
下面讨论如下场景:
一个类从两个父类中进行继承,且这两个父类分别都有一个虚函数,记父类的名称分别为α,β,在子类中也定义了一个新的虚函数
那么这个子类中有三个部分:
第一部分包括:α对应的数据和方法 和 α对应的虚函数表指针
第二部分包括:β对应的数据和方法 和 β对应的虚函数表指针
第三部分包括:专属于子类的数据和方法
虚函数表指针要等到该类在创建实例时执行初始化列表才能确定,这种行为也称为动态绑定
但是虚函数表是在编译阶段就已经由类的设计确定了的,虚函数表本质是一个函数指针数组
每个类都在编译期间分配了一个专属于这个类的函数指针数组,如果用这个类创建了多个对象,这些对象对应的函数指针数组是相同的
第一部分的虚表指针指向的虚表(也就是函数指针数组)中有两个元素
第一个函数指针:指向α中定义的虚函数,
第二个函数指针:指向子类中定义的新的虚函数
第二部分的虚表指针指向的虚表中有只有一个函数指针,指向α中定义的虚函数
好了,看了上面的内容,做一下下面这两道题吧
虚函数表和虚函数表指针
虚函数表是一个函数指针数组,存放在常量区(或称代码段)中,
只要一个类或者其父类有虚函数存在,则一定存在至少一个虚函数表指针
虚函数表指针与类的位置关系,虚函数表指针是类实例化对象后最先开辟空间的成员变量
父类和子类虚函数表指针的关系
下面讨论如下场景,注意:下面的讨论和虚函数是否构成重写无关
父类α中已经存在一个虚函数f, 且父类中没有继承其他任何的类,子类α1在类中重写了f,而且α1中不存在其他虚函数
父类创建的实例会存放虚表指针p1 和 父类所有的成员变量
子类创建的实例会分为两部分内存空间存放成员变量
第一部分:虚表指针p2 和 父类所有的成员变量
第二部分:子类所有成员变量
在这一场景中,p1和p2不同,子类和父类是两个不同的类,p1 != p2
好了,看了上面的内容,做一下下面这道题吧
抽象类不能定义实例,但是可以有指向抽象类的指针
is-a 和 has-a
在继承中,子类是一个特殊的父类。在组合中,两个类的关系更可能是一种包含的关系
比如,奔驰是一种汽车,此时奔驰类和汽车类的关系被称作 is-a 。
再比如,汽车都有轮胎,此时汽车类和轮胎类的关系被称作 has-a。
在一个类的一开始加上final,可以使得这个类不能被继承
好了,看了上面的内容,做一下下面这道题吧
继承权限
class 关键字定义的类,如果有其他子类进行继承,可以不写继承方式,默认为 private 继承
struct 关键字定义的类,如果有其他子类进行继承,可以不写继承方式,默认为 public 继承
子类在继承了父类的私有成员之后,如果创建一个子类对象,子类对象存储的内存空间中会给这些父类中的私有成员专门开辟空间来存储,但是子类对象没有访问权限
好了,看了上面的内容,做一下下面这道题吧
重定义和函数重载
子类继承了父类之后,子类的类域被划分成了两个作用域,两个作用域之间互不干扰
互不干扰是指可以有以下情况发生
1. 两个作用域中分别定义两个变量,这两个变量的名称和类型相同,假设此变量名为 v
2. 两个作用域中分别定义两个函数,这两个函数的名称完全相同,假设此函数名为 f
这两种情况有一种发生,都发生了重定义,注意2. 中对于参数列表和返回值的异同情况不作关注
此时创建一个子类对象,
当子类对象访问 v 时,会默认访问子类新的作用域中的那个 v
当子类对象调用 f 时,会默认访问子类新的作用域中的那个 f
如果想要子类旧的作用域中的 v 和 f,只需要在调用时在前面加上 父类名 ::
在一个作用域中,如果出现了两个同名函数,而且这两个同名函数的参数列表不同,那么此时发生了函数重载,注意此时对于函数返回值的一同情况不作关注
此时调用这个作用域中的函数,要根据函数调用时,参数列表的匹配情况确定调用哪个函数
好了,看了上面的内容,做一下下面这道题吧
子类和父类中构造函数和析构函数的关系
针对子类对象在创建时,以及生命周期到了以后结束时这两个阶段分别进行讨论
子类对象在创建时,分两种情况
情况一:子类的初始化列表中没有关于父类构造的代码
此时先调用父类的 默认 构造函数,再调用子类的构造函数
情况二:子类的初始化列表中存在关于创建父类对象的代码
此时先调用父类的构造函数,再调用子类的构造函数
注意:此时调用父类的构造函数一定是通过父类的指针进行调用的
子类对象在销毁时,
此时先调用子类中的析构函数,再调用父类中的析构函数
注意:父类中的析构函数最好设计成虚函数,保证子类对象在销毁时,不会造成内存泄漏的问题
好了,看了上面的内容,做一下下面这道题吧
静态成员的继承结果
静态成员函数也会被继承
静态成员变量在整个继承体系中只有一份,不属于任何对象,也就是说,在类实例化之后,调用sizeof(该类),结果不会考虑静态成员变量的大小
好了,看了上面的内容,做一下下面这两道题吧
菱形继承需要注意的事项
菱形继承讨论的是如下场景:
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
好了,看了上面的内容,做一下下面这道题吧
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;
};
---------------------------------------------------------------------------------------------------------------------------
以下是程序结果题
(倒反天罡) 程序结果题答案
一、0 1 2
二、A B C D
三、C
四、B
好了,做一下下面的程序结果题吧
题目一:
题目二: