本文主要是介绍C++ 多态 虚表原理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
C++ 多态 虚表的原理
前言
C++有三大特性,封装、继承、多态。我们今天要说到的虚表就是实现虚函数调用机制的,而虚函数又是实现多态的基础。虚函数的函数指针存储于虚表中,一个类调用虚函数其实就是访问它的虚表。
在这里我说一下多态的理解,多态其实就是父类类型的指针指向其子类的实例,这时通过父类指针调用子类的方法就可以实现不改变代码的情况下调用不同的方法,通过实例化不同的子类,父类就具有不同的形态。
我们一步步的来分析C++的虚表,首先看C++对象的内存布局。假设现在有这么个类A:
class A {public:A(int a = 0, int b = 0) :A_a(a), A_b(b){}void A1() {}private:int A_a;int A_b;
};
我们来看看类A的内存布局(IDE用的是VS2015):
我们可以看到类A的内存布局中只存在A_a和A_b。
现在有一个类B继承类A,
class B : public A
{
public:B(int a = 0, int b = 0, int c = 0) :A(a, b), B_a(c){}void B1() {}private:int B_a;
};
看一下类B的内存布局:
在这儿说明一下,在调用类B的构造之前会先调用类A的构造函数,也就是先构造父类,再构造子类,所以类B的内部布局如上图,类A的成员变量加上类B的成员变量。
以上说的是类没有虚函数的情况,现在我们给类加上虚函数,类A:
class A {public:A(int a = 0, int b = 0) :A_a(a), A_b(b){}virtual void A1() { std::cout << "A::A1" << std::endl; }virtual void A2() { std::cout << "A::A2" << std::endl; }private:int A_a;int A_b;
};
此时看一下类A的内存布局:
我们可以看到是类A的内存布局中多出了一个指针,由于类A中有虚函数,所以会创建一个虚函数表,而这个指针指向的就是类A的虚函数表,且虚函数表指针的位置在对象首位置(大多数编译器都是讲其放在对象首位置)。
我们给继承类A的B类也加上虚函数,它重写类A的A1方法,并且加上自己的虚函数B1方法,类B:
class B : public A
{
public:B(int a = 0, int b = 0, int c = 0) :A(a, b), B_a(c){}virtual void A1() { std::cout << "B::A1" << std::endl; }virtual void B1() { std::cout << "B::B1" << std::endl; }private:int B_a;
};
此时我们来看类B的内存布局:
若是父类有虚函数,则子类会继承父类的虚函数表。子类重写了父类的虚函数,会在虚函数表里替换虚函数指针地址,虚函数表内存储的虚函数指针地址顺序是先父类的虚函数指针地址,再子类的虚函数指针地址。这里类B会继承类A的虚表,它重写了父类的A1函数。类A和类B的内存布局对照如下:
通过上图我们可以很直观的看出类A与类B的内存布局对比,由于这里编译器不能显示类B写的其它的虚函数地址,我们通过打印虚函数表来看。代码如下:
typedef void(*pfun)(); //定义函数指针
int main()
{B *b = new B(1, 2, 3);pfun fun = NULL; //函数指针变量,用于循环迭代虚函数表for (int i = 0; i < 3; i++){fun = (pfun)*((long*)*(long*)b+i);fun();}std::cout << *((long*)*(long*)b + 3) << std::endl; //输出虚函数表的结束符return 0;
}
打印类B的虚函数表如下:
多重继承
多重继承就是至少有三层继承关系,如这里的B继承A,C继承B,这里写一个类C继承类B
class C : public B
{
public:C(int a = 0, int b = 0, int c = 0, int d = 0) :B(a, b, c), C_a(d){}virtual void A2() { std::cout << "C::A2" << std::endl; }virtual void B1() { std::cout << "C::B1" << std::endl; }virtual void C1() { std::cout << "C::C1" << std::endl; }private:int C_a;
};
这里的类C的内部布局如图所示:
多重继承和单继承是类似的,就是在父类的基础上增加成员变量和更新虚函数表。
多继承
多继承下,子类所继承的父类不止一个,子类中会存在多个虚函数表,重写虚函数,则会更新对应的虚函数表,若是增加了新的虚函数,则在第一个虚函数表后增加虚函数地址。
代码如下:
class A {public:A(int a = 0, int b = 0) :A_a(a), A_b(b){}virtual void A1() { std::cout << "A::A1" << std::endl; }virtual void A2() { std::cout << "A::A2" << std::endl; }private:int A_a;int A_b;
};class B
{
public:B(int a = 0) :B_a(a){}virtual void B1() { std::cout << "B::B1" << std::endl; }virtual void B2() { std::cout << "B::B2" << std::endl; }private:int B_a;
};class C : public A, public B
{
public:C(int a = 0, int b = 0, int c = 0, int d = 0) :A(a, b), B(c), C_a(d){}virtual void A1() { std::cout << "C::A1" << std::endl; }virtual void B1() { std::cout << "C::B1" << std::endl; }virtual void C1() { std::cout << "C::C1" << std::endl; }private:int C_a;
};
在这里类C继承类A与类B,类C分别重写了类A与类C的虚函数,我们来看看编译器的显示:
从编译器可以看出,类C继承了类A与类B的虚函数表,并且在内存中的顺序是按照继承顺序来的,类C重写的虚函数分别替换了两个父类的虚函数。类C自己新定义的虚函数C1()则是在继承的第一张虚函数表后添加。类C的具体内存布局如下:
在我看来,理解内部的原理能使我们解决有时候认为的“无法理解”的问题。虽然这是一条不容易的路,但每走一步都会有收获。
这篇关于C++ 多态 虚表原理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!