说明: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的更多相关文章

  1. C++中的虚函数表是什么时期建立的?

    虚函数表是在什么时期建立的? 最近参加阿里巴巴公司的内推,面试官问了“虚函数表是在什么时期建立的?”.因为以前对虚函数表的理解不够多,所以就根据程序构建(Build)的四个过程(预编译.编译.汇编和链 ...

  2. C++中的虚函数表

    (感谢http://blog.csdn.net/haoel/article/details/1948051/) C++中的虚函数的作用主要是实现了多态的机制. 多态,简而言之就是用父类型别的指针指向其 ...

  3. C++ 中的虚函数表及虚函数执行原理

    为了实现虚函数,C++ 使用了虚函数表来达到延迟绑定的目的.虚函数表在动态/延迟绑定行为中用于查询调用的函数. 尽管要描述清楚虚函数表的机制会多费点口舌,但其实其本身还是比较简单的. 首先,每个包含虚 ...

  4. 在基类中的析构函数声明为virtual

    #include <iostream> using namespace std; class Father { public: ~Father() { cout << &quo ...

  5. c++ 继承类强制转换时的虚函数表工作原理

    本文通过简单例子说明子类之间发生强制转换时虚函数如何调用,旨在对c++继承中的虚函数表的作用机制有更深入的理解. #include<iostream> using namespace st ...

  6. 深入理解类成员函数的调用规则(理解成员函数的内存为什么不会反映在sizeof运算符上、类的静态绑定与动态绑定、虚函数表)

    本文转载自:http://blog.51cto.com/9291927/2148695 总结: 一.成员函数的内存为什么不会反映在sizeof运算符上?             成员函数可以被看作是类 ...

  7. C++ 类中有虚函数(虚函数表)时 内存分布

    虚函数表 对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的.简称为V-Table.在这个表中,主是要一个类的虚函数的地址表 ...

  8. C++ 类的存储方式以及虚函数表

    一.C++成员函数在内存中的存储方式 用类去定义对象时,系统会为每一个对象分配存储空间.如果一个类包括了数据和函数,要分别为数据和函数的代码分配存储空间.按理说,如果用同一个类定义了10个对象,那么就 ...

  9. C++虚函数表解析(图文并茂,非常清楚)( 任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法)good

    C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的指针有“多种形态”,这是一种泛型技术 ...

随机推荐

  1. CentOS6.5配置代理上网

    虚拟机vmWare下装了个CentOS,第一件事是配置代理上网,因为工作环境需要使用代理上网,但发觉与uBuntu不同,根本没有什么/etc/environment. 在网上疯狂搜索,都说要配置/et ...

  2. research plan2222

    Thank you for calling. I've been looking forward to this call for a long time.Now, let me introduce ...

  3. caffe to tensorflow alexnet model

    from kaffe.tensorflow import Network class AlexNet(Network): def setup(self): (self.feed('data') .co ...

  4. Secure CRT中解决vim高亮设置的方法

    此文主要是解决vim编程中高亮显示的.原因是: 1.默认情况下,SecureCRT是有自己的终端显示颜色.这样在我们编程中不利于阅读内容. 2.我们必须到Linux系统中进行改进才能真正解决这样的问题 ...

  5. AcWing算法基础1.3

    二分 二分分为整数二分和实数二分,其中整数二分模板有两个 模板: 整数二分模板 第一种模板将区间分为[ l , mid ]  和 [ mid + 1, r ] int bsearch_1(int l, ...

  6. Akka源码分析-Cluster-ActorSystem

    前面几篇博客,我们依次介绍了local和remote的一些内容,其实再分析cluster就会简单很多,后面关于cluster的源码分析,能够省略的地方,就不再贴源码而是一句话带过了,如果有不理解的地方 ...

  7. Vue.js中学习使用Vuex详解

    在SPA单页面组件的开发中 Vue的vuex和React的Redux 都统称为同一状态管理,个人的理解是全局状态管理更合适:简单的理解就是你在state中定义了一个数据之后,你可以在所在项目中的任何一 ...

  8. HttpFileCollection 类使用

    public ActionResult GetForm()        {            HttpRequest request = System.Web.HttpContext.Curre ...

  9. leetCode----day01---- 从排序数组中删除重复项

    需求: 给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度. 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成. ...

  10. BZOJ 3625 多项式求逆+多项式开根

    思路: RT //By SiriusRen #include <bits/stdc++.h> using namespace std; <<,mod=; int A[N],C[ ...