图解虚函数的内存模型和继承方式,虚函数表指针、虚函数表、多继承、多重继承、菱形继承、虚继承

阅读本文之前想必你对虚函数已经有过一些了解~

最近面试被问过太多次虚函数了,遂写下本文以供相互学习和交流~

关键字:虚函数、虚函数表指针、虚函数表、多继承、多重继承、菱形继承、虚继承

一、单继承:
在这里插入图片描述

这种情况比较简单,把握住三点:

  1. 每个有虚函数的类都会为该类创建一个虚函数表,且在编译时已经决定了
  2. 虚函数表属于类而不属于对象
  3. 只要父类中有虚函数,子类就会有虚函数表,且父类虚函数表和子类虚函数表不同
  • 代码如下
#include <iostream>
using namespace std;

class A{
    
    
public:
    void test1(){
    
    cout << "A::test1" << endl;};
    virtual void test2(){
    
    cout << "A::test2" << endl;};
    virtual void test3(){
    
    cout << "A::test3" << endl;};
    virtual ~A(){
    
    cout << "A::~A" << endl;};
    
};

class B:public A{
    
    
public:
    void test1(){
    
    cout << "B::test1" << endl;};
    virtual void test2(){
    
    cout << "B::test2" << endl;};
    virtual ~B(){
    
    cout << "B::~B" << endl;};
};

int main(){
    
    
    A* A_ptr_2 = new B;
    A_ptr_2->test1(); //调用A::test1
    A_ptr_2->test2(); //调用B::test2
    A_ptr_2->test3(); //调用A::test3
    delete A_ptr_2;   //析构顺序为B、A
}
  • 运行结果
A::test1
B::test2
A::test3
B::~B
A::~A
  • 其对象模型
    在这里插入图片描述

当使用父类(父类中有虚函数)指针创建子类的对象时,对象的前四个字节是虚函数表指针,虚函数表指针(对象拥有)指向虚函数表(虚函数表是类拥有而不是对象拥有),再根据虚函数表找到相应的虚函数。

二、多重继承:
在这里插入图片描述

这种情况为B类继承A类、C类继承B类,子类的虚函数表是在它的父类的虚函数表基础上改动的,即C类是在B类的虚函数表基础上覆盖,B类的虚函数表在A类的基础上覆盖

  • 代码如下
#include <iostream>
using namespace std;

class A{
    
    
public:
    void test1(){
    
    cout << "A::test1" << endl;};
    virtual void test2(){
    
    cout << "A::test2" << endl;};
    virtual void test3(){
    
    cout << "A::test3" << endl;};
    virtual ~A(){
    
    cout << "A::~A" << endl;};
    
};

class B:public A{
    
    
public:
    void test1(){
    
    cout << "B::test1" << endl;};
    virtual void test2(){
    
    cout << "B::test2" << endl;};
    virtual ~B(){
    
    cout << "B::~B" << endl;};
};

class C:public B{
    
    
public:
    void test1(){
    
    cout << "C::test1" << endl;};
    virtual void test3(){
    
    cout << "C::test3" << endl;};
    virtual ~C(){
    
    cout << "C::~C" << endl;};
};

int main(){
    
    
    A* A_ptr_1 = new A;
    A_ptr_1->test1(); //调用A::test1
    A_ptr_1->test2(); //调用A::test2
    A_ptr_1->test3(); //调用A::test3
    delete A_ptr_1;   //析构A
    
    A* A_ptr_2 = new B;
    A_ptr_2->test1(); //调用A::test1
    A_ptr_2->test2(); //调用B::test2
    A_ptr_2->test3(); //调用A::test3
    delete A_ptr_2;   //析构顺序为B、A
    
    A* A_ptr_3 = new C;
    A_ptr_3->test1(); //调用A::test1
    A_ptr_3->test2(); //调用B::test2
    A_ptr_3->test3(); //调用C::test3
    delete A_ptr_3;   //析构顺序为C、B、A
    
    return 0;
}
  • 运行结果
A::test1
A::test2
A::test3
A::~A
A::test1
B::test2
A::test3
B::~B
A::~A
A::test1
B::test2
C::test3
C::~C
B::~B
A::~A
  • 其对象模型
    在这里插入图片描述

多重继承的原则就是子类只需要关心它上一级父类的虚函数表模型,然后一层层推导:

  1. A的虚函数表中有A::test2和A::test3虚函数的地址;
  2. B的虚函数表在A的基础上覆盖,即有A::test3和B::test2(A::test2被B::test2覆盖了);
  3. 同理,C的虚函数表在B的基础上覆盖,即有C::test3(A::test3被C::test3覆盖了)和B::test2。

三、多继承:
在这里插入图片描述

一个子类继承多个父类时:

  1. 父类析构的顺序决定于它被声明的顺序例如class C:class B,class A,则析构顺序为C、A、B;若是class:class A,class B,则析构顺序为C、B、A
  2. 子类对象的模型中虚函数表指针的数量为继承父类(父类中需要有虚函数)的数量,即C类的对象有两个虚函数表指针。
  3. 父类没有而子类有的虚函数,添加到第一个虚函数表中
  • 代码如下
#include <iostream>
using namespace std;

class A{
    
    
public:
    void test1(){
    
    cout << "A::test1" << endl;};
    virtual void test2(){
    
    cout << "A::test2" << endl;};
    virtual void test3(){
    
    cout << "A::test3" << endl;};
    virtual ~A(){
    
    cout << "A::~A" << endl;};
    
};

class B{
    
    
public:
    void test1(){
    
    cout << "B::test1" << endl;};
    virtual void test2(){
    
    cout << "B::test2" << endl;};
    virtual ~B(){
    
    cout << "B::~B" << endl;};
};

class C:public A,public B{
    
    
public:
    void test1(){
    
    cout << "C::test1" << endl;};
    virtual void test2(){
    
    cout << "C::test2" << endl;};
    virtual void test4(){
    
    cout << "C::test4" << endl;};
    virtual ~C(){
    
    cout << "C::~C" << endl;};
};

int main(){
    
    
    
    A* A_ptr = new C;
    A_ptr->test1(); //调用A::test1
    A_ptr->test2(); //调用C::test2
    A_ptr->test3(); //调用A::test3
    delete A_ptr;   //析构顺序为C、B、A 注意这里~A、~B的执行仅与在C类中声明的顺序有关
    
    B* B_ptr = new C;
    B_ptr->test1(); //调用B::test1
    B_ptr->test2(); //调用C::test2
    delete B_ptr;   //析构顺序为C、B、A 注意这里~A、~B的执行仅与在C类中声明的顺序有关
    
    C* C_ptr = new C;
    C_ptr->test1(); //调用C::test1
    C_ptr->test2(); //调用C::test2
    C_ptr->test3(); //调用A::test3
    C_ptr->test4(); //调用C::test4
    delete C_ptr;   //析构顺序为C、B、A 注意这里~A、~B的执行仅与在C类中声明的顺序有关
    
    return 0;
}
  • 运行结果
A::test1
C::test2
A::test3
C::~C
B::~B
A::~A
B::test1
C::test2
C::~C
B::~B
A::~A
C::test1
C::test2
A::test3
C::test4
C::~C
B::~B
A::~A
  • 其对象模型
    在这里插入图片描述

多继承的虚函数表建立方式和单继承类似,即对每个父类进行比较并分别创建虚函数表

特殊情况:父类没有而子类有虚函数时,将其放到第一个虚函数表中。例如,A类和B类中都没有test4(),将其放入第一个虚函数表中,即虚函数指针3指向C::test4

四、菱形继承
在这里插入图片描述

多继承时很容易产生命名冲突,其中最典型的就是菱形继承。类A的成员变量和成员函数到达D时变成了两份,分别来自A->B->D和A->C->D,产生了命名冲突。其解决办法为虚继承虚继承使得派生类中只保留一份间接基类的成员。无论虚基类在继承体系中出现了多少次,派生类都只包含一份虚基类成员。

五、总结

使用父类指针指向子类对象时:

  1. 单继承

    a、父类有虚函数而子类没有同名虚函数:调用父类的虚函数;

    b、子类有的虚函数而父类没有同名虚函数:父类指针不可指向子类的虚函数;

    c、子类有父类同名的虚函数:调用子类虚函数。

  2. 多重继承

    其子类虚函数表模型是根据它的直接继承父类的虚函数表模型而创建的。

  3. 多继承

    有多少个父类,子类的对象就有多少个虚函数表和虚函数表指针,若是父类中没有的虚函数而子类有,则放入第一个虚函数表中

  4. 菱形继承

    属于多继承的一种,一般采用虚继承的方法解决,即为把基类声明为虚基类。

猜你喜欢

转载自blog.csdn.net/zxxkkkk/article/details/109363441