• 【C++】多态面试题


    在这里插入图片描述

    🚀write in front🚀
    📜所属专栏: C++学习
    🛰️博客主页:睿睿的博客主页
    🛰️代码仓库:🎉VS2022_C语言仓库
    🎡您的点赞、关注、收藏、评论,是对我最大的激励和支持!!!
    关注我,关注我,关注我你们将会看到更多的优质内容!!

    在这里插入图片描述

    前言

    面试题

    我们来看看一道biantai的面试题:
    在这里插入图片描述

    一.概念查考

    1. 下面哪种面向对象的方法可以让你变得富有(A)
      A: 继承 B: 封装 C: 多态 D: 抽象
    2. (D)是面向对象程序设计语言中的一种机制。这种机制实现了方法的定义与具体的对象无关,而对方法的调用则可以关联于具体的对象。
      A: 继承
      B: 模板
      C: 对象的自身引用
      D: 动态绑定
      这里的动态绑定就是多态
    3. 面向对象设计中的继承和组合,下面说法错误的是?(C)
      A:继承允许我们覆盖重写父类的实现细节,父类的实现对于子类是可见的,是一种静态复用,也称为白盒复用
      B:组合的对象不需要关心各自的实现细节,之间的关系是在运行时候才确定的,是一种动态复用,也称为黑盒复用
      C:优先使用继承,而不是组合,是面向对象设计的第二原则
      D:继承可以使子类能自动继承父类的接口,但在设计模式中认为这是一种破坏了父类的封装性的表现
      事实上组合用的多。
    4. 以下关于纯虚函数的说法,正确的是(A)
      A:声明纯虚函数的类不能实例化对象
      B:声明纯虚函数的类是虚基类
      C:子类必须实现基类的纯虚函数
      D:纯虚函数必须是空函数
      虚基类是菱形继承里面的知识,别搞混
    5. 关于虚函数的描述正确的是(B)
      A:派生类的虚函数与基类的虚函数具有不同的参数个数和类型
      B:内联函数不能是虚函数
      C:派生类必须重新定义基类的虚函数
      D:虚函数可以是一个static型的函数
      下面有解析
    6. 关于虚表说法正确的是(D)
      A:一个类只能有一张虚表
      B:基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表
      C:虚表是在运行期间动态生成的
      D:一个类的不同对象共享该类的虚表
      B选项应该是如果我加了一个虚函数,虚表指针就会变化。
    7. 假设A类中有虚函数,B继承自A,B重写A中的虚函数,也没有定义任何虚函数,则(D)
      A:A类对象的前4个字节存储虚表地址,B类对象前4个字节不是虚表地址
      B:A类对象和B类对象前4个字节存储的都是虚基表的地址
      C:A类对象和B类对象前4个字节存储的虚表地址相同
      D:A类和B类虚表中虚函数个数相同,但A类和B类使用的不是同一张虚表
    8. 下面程序输出结果是什么? (A)
    #include
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    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. 多继承中指针偏移问题?下面说法正确的是( C )
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    A:p1 == p2 == p3
    B:p1 < p2 < p3
    C:p1 == p3 != p2
    D:p1 != p2 != p3
    10. 以下程序输出结果是什么()

    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;
    }
    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;
    }
    class A
    {
    public:
    virtual void func(int val = 1){ std::cout<<"A->"<< val <<std::endl;}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

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

      继承的时候,成员函数都在代码段里面。在这里我们通过B的指针调用A类的test()函数,由之前类与对象的知识,每一个成员函数都隐藏了this指针,而A类的this指针类型是A*this,这个时候调用test()其实就是A*this=p,就是多态的构成。然后在通过this->func(),我们就会调用到B的func()函数,这个时候按常理来说就应该选D了,但是答案是B???

      其实啊,虚函数的重写重写的是实现,之前的三同里面的参数相同也只是只的是类型相同,与值无关。所以在多态调用时,我们这里B的func,函数的头用的是A的头,函数的实现用的是B的实现,所以这里就会产生这样的问题。其实虚函数的重写重写的是实现!

    当然上面的问题是多态导致的,如果是普通的函数调用,不符合多态,就不会出现函数头和实现分开的现象:

    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; }
    	void test() { func(); }
    };
    int main(int argc, char* argv[])
    {
    	B* p = new B;
    	p->test();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里的结果就是B->0,因为不是多态。

    其实我是觉得,多态调用的时候在要通过虚表指针找函数等一系列操作可能就会让其头和实现分离,而普通调用却可以直接找到虚函数的地址,确实挺神奇的。

    二. 问答题

    1. 什么是多态?
      答:多态就是不同的对象去做同一件事情产生了不同的结果。

    2. 什么是重载、重写(覆盖)、重定义(隐藏)?
      答:
      重载是同名函数,在参数不同的情况下写出的不同功能的函数
      重写是指派生类中有一个跟基类完全相同的虚函数(三同),对这个函数进行重写
      重定义/隐藏是指在继承时,子类写了一个和父类同名的函数,子类在调用这个同名函数的时候,就会将父类的函数直接隐藏。(只要是继承+函数名相同,直接就是重定义隐藏)

    3. 多态的实现原理?
      答:两个条件:
      必须通过基类的指针或者引用调用虚函数
      被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

    4. inline函数可以是虚函数吗?
      答:可以,不过编译器就忽略inline属性,这个函数就不再是inline,因为虚函数要放到虚表中去

    5. 静态成员可以是虚函数吗?
      答:不能,因为静态成员函数没有this指针,使用类型::成员函数的调用方式无法访问虚函数表,虚函数是用于实现运行时多态性的概念,它需要针对对象的实际类型来确定调用哪个函数版本。静态成员函数不属于对象,而是属于类本身,它们在编译时就已经确定了调用关系,不涉及对象的多态性,因此不适用于虚函数的概念。

    6. 构造函数可以是虚函数吗?
      答:不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。如果把构造函数搞成虚函数,就没办法找到虚函数表指针了。

    7. 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?
      答:可以,并且最好把基类的析构函数定义成虚函数。

    8. 对象访问普通函数快还是虚函数更快?
      答:首先如果是普通调用,是一样快的。如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。

    9. 虚函数表是在什么阶段生成的,存在哪的?
      答:虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。

    10. C++菱形继承的问题?虚继承的原理?
      答:注意这里不要把虚函数表和虚基表搞混了。

    11. 什么是抽象类?抽象类的作用?
      答:包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

    总结

      多态的知识还是非常多呢,大家下去一定要多多复习呀!

      更新不易,辛苦各位小伙伴们动动小手,👍三连走一走💕💕 ~ ~ ~ 你们真的对我很重要!最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!

    专栏订阅:
    每日一题
    C语言学习
    算法
    智力题
    初阶数据结构
    Linux学习
    C++学习
    更新不易,辛苦各位小伙伴们动动小手,👍三连走一走💕💕 ~ ~ ~ 你们真的对我很重要!最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!

    在这里插入图片描述

  • 相关阅读:
    128.《usestate与usestate区别及应用场景》
    java练习 day5
    Linux用户管理
    LoadBalancer负载均衡服务调用
    百日刷题计划 ———— DAY1
    数论
    【C++ Miscellany】继承体系非尾端类设计为抽象类
    家用厨房电器测试报告办理流程
    【Hack The Box】linux练习-- Brainfuck
    接口自动化测试实践指导(下):接口自动化测试断言设置思路
  • 原文地址:https://blog.csdn.net/qq_74310471/article/details/132488690