(C/C++学习)4.C++类中的虚函数表Virtual Table
说明:C++的多态是通过一张虚函数表(Virtual Table)来实现的,简称为V-Table。在这个表中,主要为一个类的虚函数的地址表,这张表解决了继承、覆写的问题,保证其真实反应实际的虚函数调用过程。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数。
下面介绍一下与这张虚函数表有关的几个问题:
1.普通成员函数不占存储空间,而所有虚函数入口地址存储在一张虚函数表中,由一个指针指向该虚函数表;
2.指向该虚函数表的指针位于类实例对象内存的最前面,占四个字节;
3.若子类覆写了父类的虚函数,则父类的虚函数被覆盖,即虚函数表中只存在子类的虚函数地址;否则,父类和子类的虚函数都存在于虚函数表中(当然,没有覆写父类的虚函数是毫无意义的),这就是多态形成的原因。
通过上面的介绍,我们对虚函数表有了大致的了解,下面通过一个实例来加深一下认识:
#include <iostream>
using namespace std; class base
{
public:
virtual void f(){cout<<"base::f()"<<endl;}
virtual void g(){cout<<"base::g()"<<endl;}
virtual void h(){cout<<"base::h()"<<endl;}
private:
int a;
}; //定义一个函数指针,并别名为pfunc,用时不需再加*,
typedef void (*pfunc)(void); int main()
{
base b; //C++编译器使虚函数表的指针存在于对象实例中的最前面(四个字节)
cout<<"sizeof(base) = "<<sizeof(base)<<'\t'<<"sizeof(b) = "<<sizeof(b)<<endl<<'\n'; //分别打印对象b的起始地址和虚函数表中首个函数指针指向的地址
//对象实例最前面的四个字节为指向虚函数表的指针,取内容后才为虚函数表
cout<<"&b = "<<&b<<"\t\t"<<"&VTable = "<<(int **)*(int *)(&b)<<endl<<"\n\n"; pfunc pf;
//定义一个函数指针
void(*p)(void);
//还可以这样定义一个函数指针 //虚函数表里面存放的是指向各个虚函数的指针,取内容后才是各个相应的虚函数
pf = (pfunc)*((int **)*(int *)(&b)+0);
pf();
pf = (pfunc)*((int **)*(int *)(&b)+1);
pf();
pf = (void(*)())*((int **)*(int *)(&b)+2);
pf(); cout<<"\n\n"; p = (pfunc)*((int **)*(int *)(&b)+0);
p();
p = (void(*)())*((int **)*(int *)(&b)+1);
p();
p = (void(*)())*((int **)*(int *)(&b)+2);
p(); return 0;
}
程序运行结果:

通过以上示例,我们把类实例对象b取址,然后将&b强转成int*型,然后对其取内容,取得虚函数表的地址,然后再对其取内容,就得到了第一个虚函数的地址了,然后再将其通过(int**)强转成步长为4的指针,通过加1来得到虚函数表中不同的虚函数的地址,最终强转成为函数指针,再通过该函数指针访问相应的虚函数.
5.下面我们将通过几个例子来解释一下虚函数表的存在形式,在这部分,主要弄清楚虚函数表是怎么一回事,至于程序运行结果,读者自行实验。
a.在父子类中,若子类没有对父类的虚函数进行覆写(当然,前面提到过,没有覆写父类的虚函数是毫无意义的。之所以要讲述没有覆写的情况,主要目的是为了给一个对比,在比较之下,我们可以更加清楚地知道其内部的具体实现),如下代码,
#include<iostream>
using namespace std;
class base
{
public:
virtual void func(){};
virtual void foo(){};
};
class derive:public base
{
public:
virtual void func1(){};
virtual void foo1(){};
};
int main()
{
derive d;
return 0;
}
则其虚函数表如下所示:

注意:
1.上面这个图中,我在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“/0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。
2.虚函数是按照其声明顺序放于表中的。
3.父类的虚函数在子类的虚函数前面。
b.在父子类中,若子类对父类的虚函数进行了覆写(为了对比,假设只覆写父类一个虚函数),如下代码,
#include<iostream>
using namespace std;
class base
{
public:
virtual void func(){};
virtual void foo(){};
virtual ~base(){}
};
class derive:public base
{
public:
virtual void func(){cout<<"___"<<endl;};
virtual void foo1(){};
virtual ~derive(){}
};
int main()
{
base *p = new derive;
p->func();
delete p;
return 0;
}
则其虚函数表如下所示:

由此,可得覆写的子类func()放在了虚函数表中原来父类func()的位置,没有覆写的虚函数依旧原样存放。这样,在上述代码中,由于p所指的func()的位置已经被derive::func()的函数地址所取代,因此在发生实际调用的时候,调用的是子类的func(),这就实现了多态。
(C/C++学习)4.C++类中的虚函数表Virtual Table的更多相关文章
- C++中的虚函数表是什么时期建立的?
虚函数表是在什么时期建立的? 最近参加阿里巴巴公司的内推,面试官问了“虚函数表是在什么时期建立的?”.因为以前对虚函数表的理解不够多,所以就根据程序构建(Build)的四个过程(预编译.编译.汇编和链 ...
- C++中的虚函数表
(感谢http://blog.csdn.net/haoel/article/details/1948051/) C++中的虚函数的作用主要是实现了多态的机制. 多态,简而言之就是用父类型别的指针指向其 ...
- C++ 中的虚函数表及虚函数执行原理
为了实现虚函数,C++ 使用了虚函数表来达到延迟绑定的目的.虚函数表在动态/延迟绑定行为中用于查询调用的函数. 尽管要描述清楚虚函数表的机制会多费点口舌,但其实其本身还是比较简单的. 首先,每个包含虚 ...
- 在基类中的析构函数声明为virtual
#include <iostream> using namespace std; class Father { public: ~Father() { cout << &quo ...
- c++ 继承类强制转换时的虚函数表工作原理
本文通过简单例子说明子类之间发生强制转换时虚函数如何调用,旨在对c++继承中的虚函数表的作用机制有更深入的理解. #include<iostream> using namespace st ...
- 深入理解类成员函数的调用规则(理解成员函数的内存为什么不会反映在sizeof运算符上、类的静态绑定与动态绑定、虚函数表)
本文转载自:http://blog.51cto.com/9291927/2148695 总结: 一.成员函数的内存为什么不会反映在sizeof运算符上? 成员函数可以被看作是类 ...
- C++ 类中有虚函数(虚函数表)时 内存分布
虚函数表 对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的.简称为V-Table.在这个表中,主是要一个类的虚函数的地址表 ...
- C++ 类的存储方式以及虚函数表
一.C++成员函数在内存中的存储方式 用类去定义对象时,系统会为每一个对象分配存储空间.如果一个类包括了数据和函数,要分别为数据和函数的代码分配存储空间.按理说,如果用同一个类定义了10个对象,那么就 ...
- C++虚函数表解析(图文并茂,非常清楚)( 任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法)good
C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的指针有“多种形态”,这是一种泛型技术 ...
随机推荐
- Cocos2d-x 3.x 图形学渲染系列十五
笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家.特邀编辑,畅销书作者,国家专利发明人;已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D ...
- mac 下安装caffe(二)
使用Anaconda Python 1.brew edit opencv args << "-DPYTHON_LIBRARY=#{py_lib}/libpython2.7.#{d ...
- [NHibernate] 入门实例 NHibernate 3.3 GA + VS2010 +MySQL
题外话: 中国的技术论坛要赶上stackoverflow的水平.至少还需35年.中国程序猿笔者的特点是:太浮躁,太easy下总结.太自得其乐,虽说写的是为了让别人更好的看.却也演变成了一种" ...
- MT4调用Windows API进行文件读写操作
/*导入相关函数*/ #import "kernel32.dll" int CreateDirectoryW(string directoryName,int type); int ...
- influx测试——单条读性能很差,大约400条/s,批量写性能很高,7万条/s,总体说来适合IOT数据批量存,根据tag查和过滤场景,按照时间顺序返回
测试准备 需要将InfluxDB的源码放入 go/src/github.com/influxdata 目录 单写测试代码(write1.go): package main import ( " ...
- imagebutton 设置了src属性的图片更换
<ImageButton android:id="@+id/mediacontroller_play_pause" android:layout_width="wr ...
- hdu 6198(矩阵快速幂)
number number number Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Oth ...
- RegisterAttached 两种绑定方式
RegisterAttached 含义:使用指定的属性名称.属性类型和所有者类型注册附加属性 绑定方式:C#绑定.WPF绑定 例:需求DataViewModel为DataView的VM层,在DataV ...
- MySQL-ProxySQL中间件(一)| ProxySQL基本概念
目录 MySQL-ProxySQL中间件(一)| ProxySQL基本概念: https://www.cnblogs.com/SQLServer2012/p/10972593.html ...
- HttpPostedFileBase 基类
public void uploadDocMentSave(string Type) { if (Request.Files.Count > 0) { Htt ...