目录
虚函数表
多态中的虚函数的调用涉及到了一个虚函数表的知识
#include<iostream>
using namespace std;
class A
{
public:
virtual void f1() {}
virtual void f2() {}
public:
int _a;
};
class B :public A
{
public:
virtual void f1() {}
virtual void f2() {}
public:
int _b;
};
int main()
{
B b;
cout << sizeof(b) << endl;
return 0;
}
通过监视窗口我们可以发现对象b多了一个指针,这个指针里面存了一些地址
这个指针实际上就是数组指针,存的地址就是虚函数的地址、
我们可以得知以下的一些知识
1、每个含有虚函数的类都会至少有一个虚函数表,里面存放的是虚函数的地址
2、虚标表质是一个指针数组,数组的最后一个元素存nullptr,标志着虚表的结束
3、虚表的生成:先创建基类的虚表,假如派生类重写了基类的虚函数,则进行覆盖,最后将派生类独有的虚函数放在虚表的最后
4、虚函数存在代码段的,不存在对象,虚表在vs下也是存在与代码段的,指向虚表的指针存在与对象,所以对象只存了虚表的地址
5、一个类创建出的多个对象共用一张虚函数表,因为虚函数表存在代码段,也就是常量区
所以大小为24,先继承基类的成员变量,在继承虚表,最后是派生类的成员变量,大小的计算遵循类的对齐规则,所以是24
虚函数表和多继承
当派生类中有没有构成重写的虚函数时候,虚函数的地址就会存放在第一个继承的虚函数表中
虚函数表和虚基表
#include<iostream>
using namespace std;
class A
{
public:
virtual void f1(){}
int _a;
};
class B:virtual public A
{
public:
virtual void f1() {}
int _b;
};
class C :virtual public A
{
public:
virtual void f1() {}
int _c;
};
class D :public B,public C
{
public:
virtual void f1() {}
int _d;
};
int main()
{
D b;
b._a = 1;
b._b = 2;
b._c = 3;
b._d = 4;
return 0;
}
我们发现虚基表首位置存的是0,这个位置实际上存的是虚函数表距离虚基表的偏移量,但是由于构成了重写,所以b和c没有生成自己专有的虚函数表,用的是基类的虚表覆盖
#include<iostream>
using namespace std;
class A
{
public:
virtual void f1(){}
int _a;
};
class B:virtual public A
{
public:
virtual void f1() {}
virtual void f2() {} //增加了自己的虚函数
int _b;
};
class C :virtual public A
{
public:
virtual void f1() {}
virtual void f2() {} //增加了自己的虚函数
int _c;
};
class D :public B,public C
{
public:
virtual void f1() {}
int _d;
};
int main()
{
D b;
b._a = 1;
b._b = 2;
b._c = 3;
b._d = 4;
return 0;
}
当B和C增加了自己的虚函数时候,这个时候,就会创建自己的专属虚函数表,来存在这个增加的虚函数
我们通过虚基表的地址,发现前面存的是f8ffffff(-8),也就是距离虚函数表的偏移量
多态的原理
当我们用不同的对象去调用虚函数的时候,假如虚函数进行了重写,就会覆盖掉基类的虚表,从而通过虚表的地址找到函数,进行函数调用,从而形成多态
多态的调用的函数,是在运行时候通过地址调用的,而非多态的函数,调用在编译时候就进行了确定
动态绑定和静态绑定
静态绑定:程序编译时候就确定了程序的行为,例如:函数重载
动态绑定:程序运行时候才确定程序的行为,动态绑定只有在虚函数中才有体现
因为程序在编译的时候不会进行类型的检查,只会检查语法,不知道父类的指针或者引用指向什么对象,只有在运行时期根据调用对象的类型进行类型的检查,从而知道指向哪个对象,从而从对象的虚基表中调用不同的虚函数
虚基表的注意事项和知识点
1、虚表是在初始化链表初始化的,在编译阶段生成
2、inline函数可以是虚函数,当它是虚函数的时候,编译器会去掉它的inline属性
3、静态成员函数不可以是虚函数,没有this指针
4、构造函数不可以是虚函数,因为虚函数表是在初始化链表初始化的,起冲突了
5、析构函数可以是虚函数,也最好设置成虚函数,这样使用派生类的基类指针或者引用,就会调用派生类自己的析构函数了