C++虚函数,虚函数表,虚继承,虚继承表
一、虚函数
类中用virtual关键字修饰的函数。
作用:主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。
class Vfptr_classA{
public:virtual void function1(){cout<<"function1"<<endl;}virtual void function2(){cout<<"function2"<<endl;}
};
二、虚函数表
1、虚函数表格大小
先看空类的大小
//空类
class Vfptr_classA{
};int main(){Vfptr_classA Vfptr_A;cout << "sizeof Vfptr_A :" << sizeof(Vfptr_A) <<endl;return 0;
}##打印
sizeof Vfptr_A :1
2、有一个虚函数的类
//有一个虚函数的类
class Vfptr_classA{
public:virtual void function1(){cout<<"function1"<<endl;}
};int main(){Vfptr_classA Vfptr_A;cout << "sizeof Vfptr_A :" << sizeof(Vfptr_A) <<endl;return 0;
}##打印
sizeof Vfptr_A :4
3、有2个虚函数的类
//虚函数表 Vfptr
class Vfptr_classA{
public:virtual void function1(){cout<<"function1"<<endl;}virtual void function2(){cout<<"function2"<<endl;}
};int main(){Vfptr_classA Vfptr_A;cout << "sizeof Vfptr_A :" << sizeof(Vfptr_A) <<endl;return 0;
}##打印
sizeof Vfptr_A :4
由此可以正式,类在内存中记录虚函数是以一个指针记录的,并且该指针指向一个数组,数组中装着的是虚函数的地址。同时,经过实验,发现32bit的编译器下,虚函数表的指针大小是4字节。64bit的编译器下,虚函数表的指针大小是8字节。
2、虚函数表在类内存中的位置
首先我们通过代码分析一下:
//虚函数表 Vfptr
class Vfptr_classA{
public:int a;virtual void function1(){cout<<"function1"<<endl;}virtual void function2(){cout<<"function2"<<endl;}void show(){cout<<"show"<<endl;}};int main(){
//虚函数表Vfptr_classA Vfptr_A;cout << "address Vfptr_A :" << &Vfptr_A <<endl;cout << "address Vfptr_A.a :" << &Vfptr_A.a <<endl;
return 0;
}##打印
address Vfptr_A :00EFF9F8
address Vfptr_A.a :00EFF9FC
可以看到,成员变量到类的首地址有4个字节(虚函数表指针),成员变量的地址在虚函数表后,由此可以通过取类变量地址的方式获取虚函数表地址。
会有同学问,如果有成员函数呢??成员函数不占用对象的内存。这是因为所有的函数都是存放在代码区的,不管是全局函数,还是成员函数。
3、如何利用虚函数表调用虚函数
直接上代码
//虚函数表 Vfptr
class Vfptr_classA{
public:virtual void function1(){cout<<"function1"<<endl;}virtual void function2(){cout<<"function2"<<endl;}
};typedef void (*fun)(); //定义函数指针int main(){//虚函数表Vfptr_classA Vfptr_A;int * Vfptr_As = (int *)(*((int *)&Vfptr_A)); //获取虚函数表//实际上是一个数组for(int i = 0 ; Vfptr_As[i] != 0 ; ++i){cout<< "fptr "<< i <<"address :"<<Vfptr_As[i] <<endl;fun f = (fun)Vfptr_As[i];f();}return 0;
}##打印
fptr 0address :11080325
function1
fptr 1address :11080335
function2
注意:以上代码为32位,虚函数表指针为4字节。而若用64为编译器运行,则会获取地址失败,原因是64位编译器时,虚函数表指针为8字节,需要用long long类型指针获取。
Vfptr_classA Vfptr_A;long long * Vfptr_As = (long long *)(*((long long *)&Vfptr_A));cout<< "Vfptr :"<<Vfptr_As<<endl;for(int i = 0 ; Vfptr_As[i] != 0 ; ++i){cout<< "fptr "<< i <<"address :"<<Vfptr_As[i] <<endl;fun f = (fun)Vfptr_As[i];f();}##打印
fptr 0address :140695608234229
function1
fptr 1address :140695608233989
function2
4、继承类多态(虚函数重写)
1、继承
class Vfptr_classA{
public:virtual void function1(){cout<<"function1"<<endl;}virtual void function2(){cout<<"function2"<<endl;}
};class Vfptr_classB : public Vfptr_classA{
public:virtual void function3(){cout<<"function3"<<endl;}int b;
};##主函数执行Vfptr_classB Vfptr_B;cout << "sizeof Vfptr_B :" << sizeof(Vfptr_B) <<endl;cout << "address Vfptr_B :" << &Vfptr_B <<endl;cout << "address Vfptr_B.b :" << &Vfptr_B.b <<endl;##打印
sizeof Vfptr_B :8
address Vfptr_B :009DFB74
address Vfptr_B.b :009DFB78
由上可知,派生类B在头部也有一张虚函数表,大小为4字节(32bit),成员变量在后。而通过调试看
发现,派生类头部共用基类的虚函数列表,而派生类新增加的虚函数会保存在基类的虚函数后面。
2、重写
class Vfptr_classB : public Vfptr_classA{
public:virtual void function3(){cout<<"function3"<<endl;}void function1(){cout<<"ClassB function1"<<endl;}int b;
};##主函数执行
int main(){Vfptr_classB Vfptr_B;int * Vfptr_Bs = (int *)(*((int *)&Vfptr_B));cout<< "Vfptr :"<<Vfptr_Bs<<endl;for(int i = 0 ; Vfptr_Bs[i] != 0 ; ++i){cout<< "fptr "<< i <<"address :"<<Vfptr_Bs[i] <<endl;fun f = (fun)Vfptr_Bs[i];f();}return 0;
}
##打印
fptr 0address :8589982
ClassB function1
fptr 1address :8589957
function2
fptr 2address :8589637
function3
发现变化了,由打印顺序看,由于派生类B重写了基类A的function1,重写后的函数会被替换到虚函数列表中基类A的function1的位置。看一下调试
证实了我们的猜测。
3、多重继承
1、重写一个基类的虚函数
class Vfptr_classA{
public:virtual void function1(){cout<<"function1"<<endl;}virtual void function2(){cout<<"function2"<<endl;}
};class Vfptr_classC{
public:virtual void function4(){cout<<"function4"<<endl;}
};
class Vfptr_classB : public Vfptr_classA , public Vfptr_classC{
public:virtual void function3(){cout<<"function3"<<endl;}void function1(){cout<<"ClassB function1"<<endl;}int b;
};int main(){Vfptr_classB Vfptr_B;cout << "sizeof Vfptr_B :" << sizeof(Vfptr_B) <<endl;cout << "address Vfptr_B :" << &Vfptr_B <<endl;cout << "address Vfptr_B.b :" << &Vfptr_B.b <<endl;return 0;
}##打印
sizeof Vfptr_B :12
address Vfptr_B :00B3F74C
address Vfptr_B.b :00B3F754
打开调试内存
可以看到,此时在B的内存中,头部是基类A的虚函数表,跟着的是基类B的虚函数表,它们各占用4个字节。同时,重写基类A的function1函数,会覆盖原来基类A的function1函数在虚函数表的位置。
猜想:在B中重写基类C的function4,是否也会覆盖基类C中的funciton4在虚函数表的位置??好,我们继续做实验。
2、重写两个基类中的虚函数
class Vfptr_classA{
public:virtual void function1(){cout<<"function1"<<endl;}virtual void function2(){cout<<"function2"<<endl;}
};class Vfptr_classC{
public:virtual void function4(){cout<<"function4"<<endl;}
};
class Vfptr_classB : public Vfptr_classA , public Vfptr_classC{
public:virtual void function3(){cout<<"function3"<<endl;}void function1(){cout<<"ClassB function1"<<endl;}void function4(){cout<<"ClassB function4"<<endl;}int b;
};int main(){Vfptr_classB Vfptr_B;cout << "sizeof Vfptr_B :" << sizeof(Vfptr_B) <<endl;cout << "address Vfptr_B :" << &Vfptr_B <<endl;cout << "address Vfptr_B.b :" << &Vfptr_B.b <<endl;return 0;
}##打印
sizeof Vfptr_B :12
address Vfptr_B :00B3F74C
address Vfptr_B.b :00B3F754
猜想没错。
总结:
虚函数是动态多态(程序运行时多态,重载为静态多态)。实现方式为:
1、当类自身有虚函数,则会创建一张虚函数表,放置于类的开头,占用4个字节内存(64位为8bit)。
2、当类的父类中有虚函数,则派生类会继承父类的虚函数表,同时若派生类有新的虚函数,则会在父类的虚函数表后面追加。
3、当派生类为多重继承,并且多个父类都有虚函数,则会全部继承父类的虚函数表,顺序以继承顺序,并且派生类中新增的虚函数只会添加在第一个父类虚函数表中。
4、当派生类重写父类的虚函数,则重写后的函数会取缔原来虚函数在虚函数表中的位置。
4、虚函数运用时的理解
接着上,3.2(重写两个基类中的虚函数)中的例子
主程序中执行
##主程序中执行Vfptr_classA * ptrA = new Vfptr_classB();ptrA ->function1();##打印
ClassB function1
猜想:
声明一个基类A的指针ptrA ,实例化出派生类B的对象。
而ptrA 指针类型是基类A,则只能索引到派生类B中的基类A的部分:
即此时,ptrA 应该指向的是派生类B中函数列表
但是
当我们调试的时候发现
ptrA指针指向B的整个内存空间,只是不能通过
这种方式调用其他派生类的函数。虽然我们依然可以通过地址偏移,找到B空间的第二张表(基类C的虚函数表),但不建议这样用,代码可读性变差。
Vfptr_classA * ptrA = new Vfptr_classB();int * Vfptr_Bs = (int *)(*((int *)ptrA + 1)); //通过把(int *)ptrA + 1来获取下一张表for(int i = 0 ; Vfptr_Bs[i] != 0 ; ++i){cout<< "fptr "<< i <<"address :"<<Vfptr_Bs[i] <<endl;fun f = (fun)Vfptr_Bs[i];f();}
三、虚继承
虚继承和虚函数不相关!!!
虚继承是喂了解决菱形继承问题的一种手段。
1、菱形继承问题
class Vbptr_classA{
public:int a;
};class Vbptr_classB : public Vbptr_classA{
public:int b;
};class Vbptr_classC : public Vbptr_classA{
public:int c;
};class Vbptr_classD : public Vbptr_classB , Vbptr_classC{
public:int d;
};int main()
{Vbptr_classD D;D.a = 1;return 0;
}
当D调用基类A中的a成员,则会报错
原因是:D实例化时会先实例化B,C 。B,C实例化又会先实例化基类A,则最终,D中会存在两个int a;
2、虚继承
class Vbptr_classA{
public:int a;
};class Vbptr_classB : virtual public Vbptr_classA{
public:int b;
};class Vbptr_classC : virtual public Vbptr_classA{
public:int c;
};class Vbptr_classD : public Vbptr_classB , Vbptr_classC{
public:int d;
};int main()
{Vbptr_classD D;return 0;
}
观察内存
一看,同样是B里面有A,C里面也有A,后面多了一个基类A。但是细心的就可以发现,B中的A的地址与C中A的地址是与最后面的基类A的地址是一致的。所以我们可以得出结论,以这种方式的虚继承时,B和C中会创建一张虚基表,记录的是基类A的位置,记录方式是以地址+偏移量的方式记录,而真正A的实例化部分会放在最后。则由此就可以解决空间拷贝的问题。
3、虚基表(虚表)的理解
未写完。。
C++虚函数,虚函数表,虚继承,虚继承表相关推荐
- python虚函数_virtual(虚函数) vtbl(虚函数表)与vptr(虚函数表指针)
类的虚函数表是一块连续的内存,每个内存单元中记录一个JMP指令的地址 注意的是,编译器会为每个有虚函数的类创建一个虚函数表,该虚函数表将被该类的所有对象共享.类的每个虚成员占据虚函数表中的一行.如果类 ...
- c++ 虚函数多态、纯虚函数、虚函数表指针、虚基类表指针详解
文章目录 静态多态.动态多态 虚函数 哪些函数类型不可以被定义成虚函数? 虚函数的访问方式 析构函数中的虚函数 虚函数表指针 vptr 多继承下的虚函数表 虚基类表指针 bptr 纯虚函数 抽象类 虚 ...
- 构造函数不可以声明为虚函数,析构函数可以声明为虚函数
构造函数不能声明为虚函数,而析构函数可以声明为虚函数,在有的情景下析构函数必须声明为虚函数. 不建议在构造函数和析构函数里调用虚函数. 构造函数不能声明为虚函数的原因? 构造一个对象时,必须知道对象 ...
- 构造函数不可以是虚函数;析构函数可以是虚函数,也可以是纯虚函数。
构造函数不可以是虚函数:析构函数可以是虚函数,也可以是纯虚函数. 一:构造函数不能声明为虚函数的原因 1 构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的. 而在构造 ...
- C++虚函数表(vtable)和虚函数指针(vfptr)
编译器会构建一张虚表( vtable ),每一个类都有自己独特的虚表.同时,在这个继承链上,编译器会为基类插入一个隐式的指针(一般是对象的首地址),指向虚表,称为__vptr.然后,子类继承父类时,会 ...
- 虚函数表指针,虚函数表
http://hi.baidu.com/izpjjndrijbehqe/item/2a3823be221cc1d485dd797d 转载于:https://blog.51cto.com/peacefu ...
- java虚拟机 函数表_java虚拟机 jvm 局部变量表实战
java局部变量表是栈帧重要组中部分之一.他主要保存函数的参数以及局部的变量信息.局部变量表中的变量作用域是当前调用的函数.函数调用结束后,随着函数栈帧的销毁.局部变量表也会随之销毁,释放空间. 由于 ...
- C++对象内存布局--③测试多继承中派生类的虚函数在哪一张虚函数表中
C++对象内存布局--③测试多继承中派生类的虚函数在哪一张虚函数表中 测试2:证明派生类的虚函数的地址跟第一基类的虚函数地址保存在同一张虚函数表中. 派生类有多少个拥有虚函数的基类,派生类对象就有多少 ...
- C++ 面向对象(二)多态 : 虚函数、多态原理、抽象类、虚函数表、继承与虚函数表
目录 多态 多态的概念 多态的构成条件 虚函数 虚函数的重写 协变(返回值不同) 析构函数的重写(函数名不同) final和override final override 重载, 重写, 重定义对比 ...
- 虚函数、虚函数表、虚继承
1.虚函数 虚函数的定义: 虚函数必须是类的 非静态成员函数(且非构造函数),其访问权限是public(可以定义为privateor proteceted, 但是对于多态来说,没有意义),在基类的类定 ...
最新文章
- 卡巴斯基安全浏览器_卡巴斯基 for windows 全方位安全软件2021注册表清除版
- 第三届“达观杯”文本智能信息抽取挑战赛丰厚奖金,群英集结,等你来战!...
- xpath定位元素方法_测试数十次,总结了APP元素定位的万能方法
- 维特比算法—打字输入预测
- php中pandans,Python地信专题 | 基于geopandas的空间数据分析-文件IO篇
- haproxy+keepalived实现负载均衡及高可用
- python sqlite connection
- python中的@、-、*arg、**kwarg使用方法[探索1]
- python调用curl_Python3模拟curl发送post请求操作示例
- 搭建IPv6网络环境
- csgo服务器显示连接中,CSGO提示连接到官方任意服务器失败怎么办?
- 2022-02-23 安卓开发七年面试题总结
- 【软考】下午题 解题思路总结
- 归并排序及其优化(数组归并/链表归并,自顶向下/自底向上等)
- Python PDF转JPG
- 数据结构 -- 栈的基本操作(入栈、出栈、取栈顶元素)
- 在Linux上搭建nim服务器安装AIX
- Python爬虫实战(3):安居客房产经纪人信息采集
- 购买计算机一定要追求独立显卡,购买电脑的常识.pptx
- 力扣 532. 数组中的 k-diff 数对