【C++进阶之路】继承与多态的概念考察

一、问答题

  1. 什么是菱形继承?菱形继承的问题是什么?
  2. 什么是菱形虚拟继承?如何解决数据冗余和二义性的。
  3. 继承和组合的区别?什么时候用继承?什么时候用组合?
  4. 什么是多态?
  5. 什么是重载、重写(覆盖)、重定义(隐藏)?
  6. 多态的实现原理?
  7. inline函数可以是虚函数吗?
  8. 静态成员可以是虚函数吗?
  9. 构造函数可以是虚函数吗?
  10. 析构函数可以是虚函数吗?
  11. 对象访问普通函数快还是虚函数更快?
  12. 虚函数表是在什么阶段生成的,存在哪的?
  13. C++菱形继承的问题?虚继承的原理?
  14. 什么是抽象类?抽象类的作用?

二、概念题

  1. 下面哪种面向对象的方法可以让你变得富有( )
    A: 继承 B: 封装 C: 多态 D: 抽象

  1. ( )是面向对象程序设计语言中的一种机制。这种机制实现了方法的定义与具体的对象无关,而对方法的调用则可以关联于具体的对象。
    A: 继承  B: 模板  C: 对象的自身引用  D: 动态绑定

  1. 面向对象设计中的继承和组合,下面说法错误的是?()
    A:继承允许我们覆盖重写父类的实现细节,父类的实现对于子类是可见的,是一种静态复用,也称为白盒复用
    B:组合的对象不需要关心各自的实现细节,之间的关系是在运行时候才确定的,是一种动态复用,也称为黑盒复用
    C:优先使用继承,而不是组合,是面向对象设计的第二原则
    D:继承可以使子类能自动继承父类的接口,但在设计模式中认为这是一种破坏了父类的封装性的表现

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

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

  1. 关于虚表说法正确的是( )
    A:一个类只能有一张虚表
    B:基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表
    C:虚表是在运行期间动态生成的
    D:一个类的不同对象共享该类的虚表

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

  1. 下面程序输出结果是什么? ()
#include<iostream>
using namespace std;
class A{
    
    
public:
	A(char *s) {
    
     cout<<s<<endl; }
	~A(){
    
    }
};

class B:virtual public A
{
    
    
public:
	B(char *s1,char*s2):A(s1) {
    
     cout<<s2<<endl; }
};

class C:virtual public A
{
    
    
public:
	C(char *s1,char*s2):A(s1) {
    
     cout<<s2<<endl; }
};

class D:public B,public C
{
    
    
public:
	D(char *s1,char *s2,char *s3,char *s4):B(s1,s2),C(s1,s3),A(s1)
	{
    
     cout<<s4<<endl;}
};

int main() {
    
    
	D *p=new D("class A","class B","class C","class D");
	delete p;
	return 0;
}

A:class A class B class C class D
B:class D class B class C class A
C:class D class C class B class A
D:class A class C class B class D


  1. 多继承中指针偏移问题?下面说法正确的是( )
class Base1 {
    
     public: int _b1; };
class Base2 {
    
     public: int _b2; };
class Derive : public Base1, public Base2 {
    
     public: int _d; };
int main(){
    
    
Derive d;
Base1* p1 = &d;
Base2* p2 = &d;
Derive* p3 = &d;
return 0;
}

A:p1 == p2 == p3
B:p1 < p2 < p3
C:p1 == p3 != p2
D:p1 != p2 != p3


  1. 以下程序输出结果是什么()
class A
{
    
    
public:
	virtual void func(int val = 1){
    
     std::cout<<"A->"<< val <<std::endl;}
	virtual void test(){
    
     func();}
};
class B : public A
{
    
    
public:
	void func(int val=0){
    
     std::cout<<"B->"<< val <<std::endl; }
};
int main(int argc ,char* argv[])
{
    
    
	B*p = new B;
	p->test();
	return 0;
}

A: A->0
B: B->1
C: A->1
D: B->0
E: 编译出错
F: 以上都不正确

三、答案与解析

问答题

  1. 什么是菱形继承?菱形继承的问题是什么?
    解析:菱形继承是多继承延伸出来的问题,可概括为"一子多父,多父共一父",这样导致了数据冗余和二义性问题,最关键的在于数据冗余,如果数据量过大,则会导致内存资源浪费的问题,二义性可通过指定作用域来解决。

  2. 什么是菱形虚拟继承?如何解决数据冗余和二义性的。
    解析:从概念上来讲,菱形虚拟继承是在菱形继承的基础上,在父类继承其基类时,前加virtual的现象,从原理上来讲,通过引用虚基表和虚基表指针,加以改变对象的存储模型,使最后一个位置存的是基类,原来存基类的位置换成了虚基表指针,虚基表指针指向的是虚基表,虚基表存放的是偏移量,第一个位置存放的是虚基表指针的地址相对于this指针的偏移量,计算方式:this指针 - 虚表指针的地址(通常为0或者-4),第二个位置存放的是虚基表指针的地址相对于基类的this指针 的偏移量,第三个位置存放的可能是相较于其它基类的偏移量。最后一个位置存放的是结束位置,VS下为0。这只是一个元素要存放的信息。通过如上操作,虚表和虚基表指针来达到节省内存空间的值,总的来说还是浪费几个字节的,不过相较于数据量很大的来说就忽略不计了,其次这样设计是为了考虑通用性,一个类直接用一张统一的虚基表和虚表指针即可。

  3. 继承和组合的区别?什么时候用继承?什么时候用组合?
    解析:在实现类的多态时,我们就不得不使用继承了,因为只有继承才能实现类的多态。在大多数情况下,能考虑用组合就用组合,因为组合更加符合高内聚低耦合的概念,使代码之间更加地完整和独立。

  4. 什么是多态?
    解析:从概念上来讲,就是一种事物对应不同种形态,符合 has_a的关系,从分类上来讲,多态分为函数重载和动态绑定,从实现角度上来讲,主要是通过基类的指针和引用以及虚函数和重写,来达到通过父类的指针或引用指向子类时,能够调用子类的虚函数而不是父类的虚函数。

  5. 什么是重载、重写(覆盖)、重定义(隐藏)?
    一张图理解:
    在这里插入图片描述

  6. 多态的实现原理?
    解析:从实现角度上来讲,主要是通过基类的指针和引用以及虚函数和重写,来达到通过父类的指针或引用指向子类时,能够调用子类的虚函数而不是父类的虚函数。

    扫描二维码关注公众号,回复: 16640071 查看本文章
  7. inline函数可以是虚函数吗?
    解析: 这里我们要明确两个概念的区别内联函数和被inline关键字修饰的函数,被inline修饰的函数最终是不是内联是我们说了不算的,最终要靠编译器来决定的,虚函数前加inline并不是内联,因为如果是内联,那虚函数的地址就不会被存放在虚表中,而我们都知道虚函数是要存放在虚表中的,与概念相悖,因此虚函数可以被inline修饰,但是其不是内联函数。

  8. 静态成员可以是虚函数吗?
    解释:不是,因为静态成员没有this指针,仅通过对象和指定作用域调用即可,而多态调用是要传this指针,通过this指针调用虚函数来判断是否构成多态的条件,从而实现多态,因此不可能是虚函数。

  9. 构造函数可以是虚函数吗?
    解释:不可以,从语法上编译器就禁掉了,从原理层上来看,我们要考虑先有鸡还是先有蛋的问题,即先初始化虚表指针(蛋)再调用构造函数(鸡)。这是不现实的,因此只能通过构造函数初始化函数指针,在通过虚函数指针调用虚函数。

  10. 析构函数可以是虚函数吗?
    解释:可以,我们也推荐加上virtual,在实现多态时,如果不是虚函数,从而导致指向子类的父类指针,在进行delete时,只调用了父类的析构而没有调用子类的析构函数,这样可能会导致内存泄漏,因此可以,并强烈建议加上。

  11. 对象访问普通函数快还是虚函数更快?
    解释:普通函数在调用时,是直接call对应的函数地址,而虚函数需要先找到虚函数指针,再通过虚函数指针找到对应的地址,再进行调用,因此普通函数更快,但综合下来只在函数过多的情况下考虑这种问题,一般来说差别不大。

  12. 虚函数表是在什么阶段生成的,存在哪的?
    解释:虚函数表是在编译期间生成的,至少在VS下是存在常量区的。

  13. 什么是抽象类?抽象类的作用?
    解释:我们把具有纯虚函数的类称为抽象类,抽象类在运用角度来说,强制子类必须重写虚函数,否则无法进行示例化,因为继承了纯虚函数是无法示例化的。

概念题

在这里插入图片描述

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

猜你喜欢

转载自blog.csdn.net/Shun_Hua/article/details/132171369
今日推荐