网站建设筹备方案,网络营销名词解释答案,南通网站排名优化公司,wordpress教程下载地址前言
#xff08;1#xff09;虚基表与虚函数表是两个完全不同的概念
虚基表用来解决继承的二义性(虚基类可以解决)。虚函数用来实现泛型编程#xff0c;运行时多态。
#xff08;2#xff09;虚函数是在基类普通函数前加virtual关键字#xff0c;是实现多态的基础 1虚基表与虚函数表是两个完全不同的概念
虚基表用来解决继承的二义性(虚基类可以解决)。虚函数用来实现泛型编程运行时多态。
2虚函数是在基类普通函数前加virtual关键字是实现多态的基础 3虚函数表其实不用我们管这个编译器会帮我们做好 注无特别说明本文的虚表均指虚函数表
一 什么是虚函数表
虚函数Virtual Function是通过一张虚函数表VirtualTable来实现的。简称为V-Table。虚表(virtual table)编译器为每个拥有虚函数的类都建有一张虚函数表主是要一个类的虚函数的地址表这张表解决了继承、覆盖的问题保证其容真实反应实际的函数。这样在有虚函数的类的实例中这个表被分配在了这个实例的内存中所以当我们用父类的指针来操作一个子类的时候这张虚函数表就显得由为重要了它就像一个地图一样指明了实际所应该调用的函数。
C的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下
二含虚函数的单继承
单继承时派生类中仅有一个虚函数表。这个虚函数表和基类的虚函数表不是一个表无论派生类有没有重写基类的虚函数但是如果派生类没有重写基类的虚函数的话基类和派生类的虚函数表指向的函数地址都是相同的。
#include iostream
using namespace std;class A
{
public :A(int a){this-a a;}virtual void show(){cout a a endl;}
protected:int a;
};class B:public A
{
public:B(int a, int b) :A(a){this-b b;}
/* void show(){cout a a b b endl;}*/
protected:int b;
};int main()
{A a(2);a.show();B b(1, 3);b.show();return 0;
}此时类B没有重写类A的show方法仅仅是继承了父类 可以看出两个类的__vfptr的值不同但是每个槽内部的函数地址都是相同的。 下面在类B中重写类A的show方法
#include iostream
using namespace std;class A
{
public :A(int a) {this-a a;}virtual void show() {cout a a endl;}
protected:int a;
};class B:public A
{
public:B(int a, int b) :A(a) {this-b b;}void show() {cout a a b b endl;}
protected:int b;
};int main()
{A a(2);a.show();B b(1, 3);b.show();return 0;
}通过上面可以总结派生类内存布局先是复制一份基类内存布局然后是自己的布局注意内存对齐。虚表指针指向自己的虚表派生类虚函数地址如果自己未覆盖那么就是基类的否则是自己的函数地址并且可以看到派生类一旦重写父类的虚函数就会覆盖原来继承的 关于这一点的讲解我认为这个大佬讲的不错虚函数表解析
三含虚函数的多继承
多继承情况下派生类中有多个虚函数表虚函数的排列方式和继承的顺序一致。派生类重写函数将会覆盖所有虚函数表的同名内容派生类自定义新的虚函数将会在第一个类的虚函数表的后面进行扩充。
#includeiostream
using namespace std;
class Base
{
public:Base(int base){ this-base base;}virtual void show() {cout base endl;}virtual void print() { cout test Base endl; }
protected:int base;
};
class BaseA
{
public:BaseA(int basea){this-basea basea;}virtual void show(){cout basea endl;}virtual void watch() { cout test BaseA endl; }
protected:int basea;
};
class BaseB :public Base, public BaseA
{
public:BaseB(int base, int basea, int baseb) :Base(base), BaseA(basea){this-baseb baseb;}void show(){cout base basea baseb endl;}virtual void print() { cout test B endl; }//重写base的print方法没有重写BASEA的watch方法
private:int baseb;
};
int main()
{Base base(1);BaseA baseA(2);BaseB baseB(3,3, 3);return 0;
}这里通过编译器的部分可以看出来未被重写的虚函数指针将和基类指向同一个位置一旦被重写函数指针就指向新的位置。先按照继承顺序从左到右排布基类的布局包括虚标指针然后排布自己的指针和数据派生类虚表排布形式是按照继承顺序是继承来的虚函数如果有覆盖则换成自己的函数地址然后是下一个基类直至基类排布完毕。继承来的多张表是独立的从内存布局中的多个虚表指针可以看出且使用首地址偏移量的形式来访问。 注意如果派生类有自己的虚函数则会加在第一个基类的虚表末尾 以前关于虚函数表不太明白今天网上搜集了些资料在这里总结一下如果有错误欢迎讨论啊~
附录
这里介绍一种查看内存布局的方法 1.点击图中红圈打开“开发人员命令提示符” 2.通过dos命令进入代码所在目录 3.使用cl命令的/d1 reportAllClassLayout或reportSingleClassLayoutXXX选项。这里的reportAllClassLayout选项会打印大量相关类的信息一般用处不大。而reportSingleClassLayoutXXX选项的XXX代表要编译的代码中类的名字这里XXX类打印XXX类的内存布局和虚函数表如果代码中没有对应的类则选项无效。 例如我的 在vs中查看变量的另一种方法是 在调试模式下点击窗口自动窗口就可以了 注意一定是调试模式否则没有此按钮同时注意设置断点。
参考文章 虚函数表解析 C虚函数和虚函数表原理