虚表(虚函数表)
C++中,一个类存在虚函数,那么编译器就会为这个类生成一个虚函数表,在虚函数表里存放的是这个类所有虚函数的地址(虚表从属于类)。编译器会为包含虚函数的类加上一个成员变量,该成员变量是一个指向虚函数表的指针,因此虚表指针是一个成员属性,也就是说,如果一个类含有虚表,那么类的每个对象都含有虚表指针。当生成类对象的时候,编译器会自动的将类对象的前四个字节设置为虚表的地址(不一定哦,但是大部分是),而这四个字节就可以看作是一个指向虚函数表的指针。
虚函数表属于类,类的所有对象共享这个类的虚函数表
虚函数表存储在只读数据段(.rodata),也就是说虚函数表在编译阶段就已经形成了,虚函数表指针是在构造函数中赋值的。
注意
1) 派生类对象有两部分组成,一部分是从父类中继承到的成员(包括虚表指针),另一部分就是子类自己的成员
3) 虚函数表本质是一个存放虚函数指针的指针数组,这个数组最后面放了一个nullptr
4) 派生类的虚表的生成:a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中的某个虚函数,用派生类自己的虚函数覆盖虚表中的基类的虚函数 c.派生类自己新增的虚函数按其在派生类中的声明次序依次增加到派生类虚表的最后
5) 虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样都是存在于代码段中的,只是它的指针又存到了虚表中。对象中存的不是虚表,存的是虚表的指针。虚表也是存在于数据段的。
6) 满足多态的的函数调用是在运行时确定的(从对象中找到函数地址),而不满足多态的函数调用是编译时确定好的
7) 多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表
动态绑定和静态绑定
1) 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,例如函数重载
2) 动态绑定又称为后期绑定(晚绑定),是程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态
单继承的无重写虚函数
那么派生类将会把自己的虚函数加入到基类虚表中,按照声明次序依次加入
单继承的有重写虚函数
派生类将其重写的函数地址覆盖到虚表指定的位置上
无重写的单继承的虚拟继承
基类打印自己得虚函数,派生类只打印自己的,并不会打印基类的。
派生类对象结构为派生类虚函数指针、虚基类指针、派生类成员变量、基类虚函数指针、基类的成员变量
这里我们看到了虚基类指针其实就是指向当前位置与基类的偏移量
// 如果你想打印基类的方法
#include<iostream>
using namespace std;
class Base
{
public:
Base()
{
b = 10;
}
virtual void fun0()
{
cout << "Base::fun0()" << endl;
}
virtual void fun1()
{
cout << "Base::fun1()" << endl;
}
virtual void fun2()
{
cout << "Base::fun2()" << endl;
}
int b;
};
class Derived :virtual public Base
{
public:
Derived()
{
d = 20;
}
virtual void fun3()
{
cout << "Derived::fun3()" << endl;
}
virtual void fun4()
{
cout << "Derived::fun4()" << endl;
}
virtual void fun5()
{
cout << "Derived::fun5()" << endl;
}
int d;
};
typedef void(*vpf)();
void Printvpf()
{
Base b;
Derived d;
//Base* p = &d;
cout << "Base::vpf" << endl;
vpf* n = (vpf*)*(int *)(&b);
while (*n)
{
(*n)();
n++;
}
cout << "Derived::vpf" << endl;
vpf* pn = (vpf*)*(int *)(&d);
while (*pn)
{
(*pn)();
pn++;
}
cout << "Derived::Base vpf" << endl;
vpf* pp = (vpf*)(*(int *)((char *)(&d) + 12));
while (*pp)
{
(*pp)();
pp++;
}
cout << sizeof(Base) << endl;
cout << *((int *)(*(int*)((int*)(&d) + 1)) + 1) << endl;
cout << sizeof(Derived) << endl;
}
int main()
{
Printvpf();
system("pause");
return 0;
}
单继承的虚拟继承的有重写的虚表
基类只打印自己的,派生类将重写的不打印,只打印自己的
基类大小不变,派生类的大小和无重写几乎一样,不过多了一个用于区分对象的0,这个0加进了派生类数据成员和指向基类虚表的指针之间
#include<iostream>
using namespace std;
class Base
{
public:
Base()
{
b = 10;
}
virtual void fun0()
{
cout << "Base::fun0()" << endl;
}
virtual void fun1()
{
cout << "Base::fun1()" << endl;
}
virtual void fun2()
{
cout << "Base::fun2()" << endl;
}
int b;
};
class Derived :virtual public Base
{
public:
Derived()
{
d = 20;
}
virtual void fun0()
{
cout << "Derived::fun0()" << endl;
}
virtual void fun1()
{
cout << "Derived::fun1()" << endl;
}
virtual void fun5()
{
cout << "Derived::fun5()" << endl;
}
int d;
};
typedef void(*vpf)();
void Printvpf()
{
Base b;
Derived d;
//Base* p = &d;
cout << "Base::vpf" << endl;
vpf* n = (vpf*)*(int *)(&b);
while (*n)
{
(*n)();
n++;
}
cout << "Derived::vpf" << endl;
vpf* pn = (vpf*)*(int *)(&d);
while (*pn)
{
(*pn)();
pn++;
}
cout << sizeof(Base) << endl;
cout << sizeof(Derived) << endl;
}
int main()
{
Printvpf();
system("pause");
return 0;
}