C++虚表

虚函数

  在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数称为虚函数,语法格式:

virtual 成员函数名() 

  重写:

  • 必须是虚函数
  • 子类定义了一个和父类完全相同的成员函数,包括返回值类型、函数名、参数列表
  • 重写也称为覆盖
    上代码镇楼:
class A {
public:
    virtual void func()
    {
        cout << "A::func()" << endl;
    }
};
class B :public A {
public:
    virtual void func()
    {
        cout << "B::func()" << endl;
    }
};
int main()
{
    A* p=new B;
    p->func();//输出"B::func()",子类的func覆盖掉了父类的func
    system("pause");
    return 0;
}

虚表

  1. 虚表也称为虚函数表,顾名思义就是用来存放虚函数地址的一个抽象表格。这个表格是我们人为抽象出来的。
  一个对象可能有多个虚函数,所以虚表的本质是一个指针数组,用来存放虚函数地址的数组。这个数组的最后一个元素是0.
  编译器会为包含虚函数的类加上一个成员变量,是一个指向该虚函数表的指针(常被称为vptr),每一个由此类别派生出来的类,都有这么一个vptr。虚表指针是从属于对象的。也就是说,如果一个类含有虚表,则该类的所有对象都会含有一个虚表指针,并且该虚表指针指向同一个虚表。
  2. 虚表的内容是依据类中的虚函数声明次序填入函数指针。派生类会继承基类的虚表(以及所有其他可以继承的成员),当我们在派生类中改写虚函数时,虚表就受了影响;表中的元素所指的函数地址将不再是基类的函数地址,而是派生类的函数地址。
C++虚表_第1张图片
  3.打印虚表:
  有一句话,看到的往往不是真的,我们在监视窗口看的好像就是这么一回事,但是万一不对呢,为了解决这个困扰,我们将虚函数打印出来。怎么打呢?前面说过,虚表的本质是一个指针数组,里面存放的是放进去的虚函数地址,并且最后一个元素是0,我们就像打印数组一样打印虚表,遇0结束。

typedef void(*FUNC)();//函数指针
void PrintVTable(int* VTable)
{
    cout << " 虚表地址>" << VTable << endl;
    for (int i = 0; VTable[i] != 0; ++i)
    {
        printf(" 第%d个虚函数地址 :0X%x->", i, VTable[i]);
        FUNC f = (FUNC)VTable[i];
        f();//调用对应函数
    }
    cout << endl;
}
class Base {
public:
    Base()
        :_a(1)
    {}
    virtual void func1()
    {
        cout << "Base::func1()" << endl;
    }
    virtual void func2()
    {
        cout << "Base::func2()" << endl;
    }
private:
    int _a;
};
int main()
{
    Base b;
    int* vtable = (int*)(*(int*)&b);
    PrintVTable(vtable);
    system("pause");
    return 0;
}
/*输出结果:
 虚表地址>00F79B64
 第0个虚函数地址 :0Xf710dc->Base::func1()
 第1个虚函数地址 :0Xf7139d->Base::func2()
 */

虚表在几种继承下的情况

1.单继承:(无重写)

class Base {
public:
    Base()
        :_a(1)
    {}
    virtual void func1()
    {
        cout << "Base::func1()" << endl;
    }
    virtual void func2()
    {
        cout << "Base::func2()" << endl;
    }
private:
    int _a;
};
class Derive :public Base {
public:
    Derive()
        :_b(2)
    {}
    virtual void func3()
    {
        cout << "Derive::func3()" << endl;
    }
    virtual void func4()
    {
        cout << "Derive::func4()" << endl;
    }
private:
    int _b;
};
int main()
{
    Base b;
    Derive d;
    system("pause");
    return 0;
}

C++虚表_第2张图片

2.单继承:(有重写)

  重写子类的虚函数会覆盖掉父类对应的虚函数,所以在虚表中也会发生相应的变化。

class Base {
public:
    Base()
        :_a(1)
    {}
    virtual void func1()
    {
        cout << "Base::func1()" << endl;
    }
    virtual void func2()
    {
        cout << "Base::func2()" << endl;
    }
private:
    int _a;
};
class Derive :public Base {
public:
    Derive()
        :_b(2)
    {}
    virtual void func2()
    {
        //重写了父类base的func2函数
        cout << "Derive::func2()" << endl;
    }
    virtual void func3()
    {
        cout << "Derive::func3()" << endl;
    }
private:
    int _b;
};
int main()
{
    Base b;
    Derive d;
    system("pause");
    return 0;
}

C++虚表_第3张图片
  那么对于上述代码,我们现在就能很轻松的明白Base和Derive的大小:

cout << sizeof(Base) << endl;//8
cout << sizeof(Derive) << endl;//12

3.多继承(非菱形继承)

  对于多继承关系而言,情况又变的复杂:

class A {
public:
    virtual void func1()
    {
        cout << "A::func1()" << endl;
    }
    int _a;
};
class B {
public:
    virtual void func2()
    {
        cout << "B::func2()" << endl;
    }
    int _b;
};
class C :public B, public A {
public:
    virtual void func1()//重写父类A的虚函数func1
    {
        cout << "C::func1()" << endl;
    }
    virtual void func3()
    {
        cout << "C::func3()" << endl;
    }
    int _c;
};
int main()
{
    A a;
    B b;
    C c;
    c._a = 1;
    c._b = 2;
    c._c = 3;
    //求:
    cout << sizeof(a) << endl;
    cout << sizeof(b) << endl;
    cout << sizeof(c) << endl;
    system("pause");
    return 0;
}

  对于多继承,我们首先要明白几个概念: 

  • 如果父类都有虚表,那么子类会继承所有父类的虚表,并且将自己的虚表指针放在第一个被继承的父类的虚表中
  • 有几个含有虚表的父类,子类就有几个虚表,比如上述例子子类C继承了父类B、A,并且两个父类都有虚表,所以子类C就有两个虚表,即两个虚表指针。
  • 如果子类和父类的虚函数构成重写,则多继承的重写规则和单继承一样,即子类的虚函数会在虚表中覆盖掉父类对应的虚函数。子类重写了哪个父类的函数,子类就将这个函数存放在那个父类的虚表中。

      画图对上述代码解析:
     C++虚表_第4张图片 
     C++虚表_第5张图片
      回过头我们对上述代码的问题就能迎刃而解:

    cout << sizeof(a) << endl;//8
    cout << sizeof(b) << endl;//8
    cout << sizeof(c) << endl;//20

4.多继承(菱形继承)

  对于菱形继承而言,为了解决二义性的问题,我们引入了虚继承,即用虚基表来解决了二义性,所以在此我们需要分析虚基表和虚表。
  首先我们要严格区分开虚基表和虚表,两者毫无关系可言。虚基表是虚继承产生的,虚表是多态产生的(即必须有虚函数)。
  对于菱形继承,它也有几点规则,掌握了规则就好分析问题:

  • 子类有几个拥有虚表的父类,就拥有几张虚表
  • 有几个虚继承同一个类的父类,子类就有几个虚基表指针
  • 因为是虚继承,对于共同的父类,子类将这个父类的虚表放在公共区域。
class A//8
{
public:
    virtual void fun1()
    {
        printf("A::virtual void fun(int n)\n");
    }
    int _a;
};

class B :virtual public A//虚继承
{
public:
    virtual void fun2()//8+8+4
    {
        printf("B::virtual void fun(int n)\n");
    }
    int _b;
};

class C :virtual public A//虚继承
{
public:
    virtual void fun3()
    {
        printf("C::virtual void fun(int n)\n");
    }
    int _c;
};

class D :public C, public B
{
public://大小:虚继承情况下基类A的大小(虚表指针+int(_a))+B的大小(int(_b)+虚基表指针+虚表指针)
        //+C的大小(int(_c)+虚基表指针+虚表指针)+int(_d)
    int _d;
};

int main()
{
    A a;
    B b;
    C c;
    D d;//d有几个虚函数:
    a._a = 1;
    b._a = 2;
    b._b = 3;
    c._a = 4;
    c._c = 5;
    d._a = 6;
    d._b = 7;
    d._c = 8;
    d._d = 9;
}

C++虚表_第6张图片
C++虚表_第7张图片
  简单来说,对于菱形继承来说,子类的对象模型可以简单划分为:
  C++虚表_第8张图片
 

你可能感兴趣的:(C++学习笔记)