大家知道虚函数是通过一张虚函数表来实现的。在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,其内容真是反应实际的函数。这样,在有虚函数的类的实例中,这个表分配在了这个实例的内存中,所以,当用父类的指针来操作一个子类的时候,这张虚函数表就显得尤为重要了。它就像一个地图一样,指明了实际所应该调用的函数。

C++的标准规则中说到,编译器必须保证虚函数表的指针存在于对象实例中最前面的位置(这样是为了保证正确取到虚函数的偏移量)。这意味着通过对象实例的地址得到这张虚函数表,然后可以遍历其中的函数指针,并调用相应的函数。

#include <iostream>
using namespace std;
class Base
{
public:
virtual void fun1(){cout<<"Base::fun1\n";}
virtual void fun2(){cout<<"Base::fun2\n";}
virtual void fun3(){cout<<"Base::fun3\n";}
private:
int num1;
int num2;
}; typedef void (*Fun)(void); int main()
{
Base b;
Fun pFun;
//通过指针分别调用了对象b的3个虚函数。
pFun = (Fun)* ( (int*)*(int*)(&b)+ );
pFun();
pFun = (Fun)* ( (int*)*(int*)(&b)+ );
pFun();
pFun = (Fun)* ( (int*)*(int*)(&b)+ );
pFun();
return ;
}
/*
程序执行结果如下:
Base::fun1
Base::fun2
Base::fun3
Press <RETURN> to close this window...
*/

程序中的Base对象b内存结构如下:


一个类会有多少张虚函数表呢?

对于一个单继承的类,如果它有虚函数,则只有一张虚函数表。对于多重继承的类,它可能有多张虚函数的表。

#include <iostream>
using namespace std;
class Base1
{
public:
Base1(int num):num_1(num){}
virtual void fun1(){cout<<"Base1::fun1 "<<num_1<<endl;}
virtual void fun2(){cout<<"Base1::fun2 "<<num_1<<endl;}
virtual void fun3(){cout<<"Base1::fun3 "<<num_1<<endl;}
private:
int num_1;
};
class Base2
{
public:
Base2(int num):num_2(num){}
virtual void fun1(){cout<<"Base2::fun1 "<<num_2<<endl;}
virtual void fun2(){cout<<"Base2::fun2 "<<num_2<<endl;}
virtual void fun3(){cout<<"Base2::fun3 "<<num_2<<endl;}
private:
int num_2;
};
class Base3
{
public:
Base3(int num):num_3(num){}
virtual void fun1(){cout<<"Base3::fun1 "<<num_3<<endl;}
virtual void fun2(){cout<<"Base3::fun2 "<<num_3<<endl;}
virtual void fun3(){cout<<"Base3::fun3 "<<num_3<<endl;}
private:
int num_3;
};
class Derived1:public Base1
{
public:
Derived1(int num):Base1(num){}
virtual void fDer1_1(){cout<<"Derived1::fDer1_1\n";}//无覆盖
virtual void fDer1_2(){cout<<"Derived1::fDer1_2\n";}
};
class Derived2:public Base1
{
public:
Derived2(int num):Base1(num){}
virtual void fun2(){cout<<"Derived2::fun2 "<<endl;}//只覆盖了Base1::fun2
virtual void fDer2_1(){cout<<"Derived2::fDer2_1\n";}
virtual void fDer2_2(){cout<<"Derived2::fDer2_2\n";}
};
class Derived3:public Base1,public Base2,public Base3//多重继承,无覆盖
{
public:
Derived3(int num_1,int num_2,int num_3):Base1(num_1),Base2(num_2),Base3(num_3){}
virtual void fDer3_1(){cout<<"Derived3::fDer3_1\n";}
virtual void fDer3_2(){cout<<"Derived3::fDer3_2\n";} };
class Derived4:public Base1,public Base2,public Base3//多重继承,有覆盖
{
public:
Derived4(int num_1,int num_2,int num_3):Base1(num_1),Base2(num_2),Base3(num_3){}
virtual void fun1(){cout<<"Derived4::fun1\n";}//覆盖了所有基类的fun1函数
virtual void fDer4_1(){cout<<"Derived4::fDer4_1\n";} };
int main()
{
Base1 *pBase1 = NULL;
Base2 *pBase2 = NULL;
Base3 *pBase3 = NULL; cout<<"----- Generally inherited from Base1, no cover------\n";
Derived1 d1();
pBase1 = &d1;
pBase1->fun1(); cout<<"----- Generally inherited from Base1, covering fun2---\n";
Derived2 d2();
pBase1 = &d2;
pBase1->fun2(); cout<<"----- Multiple inheritance, no cover-----------------\n";
Derived3 d3(,,);
pBase1 = &d3;
pBase2 = &d3;
pBase3 = &d3;
pBase1->fun1();
pBase2->fun1();
pBase3->fun1(); cout<<"----- Multiple inheritance, covering fun1-------------\n";
Derived4 d4(,,);
pBase1 = &d4;
pBase2 = &d4;
pBase3 = &d4;
pBase1->fun1();
pBase2->fun1();
pBase3->fun1();
return ;
} /*
* 程序运行结果如下:
----- Generally inherited from Base1, no cover------
Base1::fun1 1
----- Generally inherited from Base1, covering fun2---
Derived2::fun2
----- Multiple inheritance, no cover-----------------
Base1::fun1 1
Base2::fun1 2
Base3::fun1 3
----- Multiple inheritance, covering fun1-------------
Derived4::fun1
Derived4::fun1
Derived4::fun1
Press <RETURN> to close this window...
*/

一般继承(无虚函数覆盖)

Derived1类继承自Base1类,没有任何覆盖基类的函数,因此Dervied1的两个虚拟函数被依次添加到了虚函数表的尾部。Derived1的虚函数表如下图:


一般继承(有虚函数的覆盖)

Derived2继承自Base1类,并对基类中的fun2()进行了覆盖。所以虚函数表中的Derived2::fun2代替了Base::fun2,用时派生类中新的虚函数添加到虚函数的表尾。Derived2的虚函数表如下图:


多重继承(无虚函数覆盖)

Derived3继承自Base1,Base2,Base3,其虚函数表如下:

Derived3的每个父类都有自己的虚表,所以Derived3也就有了3个虚表。这里父类虚表的顺序与声明继承父类的顺序一致。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。例如:

Base2 *pBase2 = new Derived3();
pBase->fun2();

把Base2类型的指针指向Derived3实例,那么调用将是对应Base2虚表里的那些函数.


多重继承(有虚函数覆盖)

Derived4类继承自Base1,Base2,Base3并对3个基类的fun1函数进行了覆盖。其虚函数表如下:

可以看见基类中的fun1都被替换成了Derived4::fun1,这样,我们就可以把任意一个静态类型的父类指向子类,并调用子类的f()了。

Base1 *pBase1 = new Derived4();
pBase1->fun1();

C++虚函数表的更多相关文章

  1. C++ 虚函数表解析

    转载:陈皓 http://blog.csdn.net/haoel 前言 C++中 的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实 ...

  2. C++ 多态、虚函数机制以及虚函数表

    1.非virtual函数,调用规则取决于对象的显式类型.例如 A* a  = new B(); a->display(); 调用的就是A类中定义的display().和对象本体是B无关系. 2. ...

  3. C++迟后联编和虚函数表

    先看一个题目: class Base { public: virtual void Show(int x) { cout << "In Base class, int x = & ...

  4. C++ 知道虚函数表的存在

    今天翻看陈皓大大的博客,直接找关于C++的东东,看到了虚函数表的内容,找一些能看得懂的地方记下笔记. 0 引子 类中存在虚函数,就会存在虚函数表,在vs2015的实现中,它存在于类的头部. 假设有如下 ...

  5. C++虚函数和虚函数表

    前导 在上面的博文中描述了基类中存在虚函数时,基类和派生类中虚函数表的结构. 在派生类也定义了虚函数时,函数表又是怎样的结构呢? 先看下面的示例代码: #include <iostream> ...

  6. C++ Daily 《5》----虚函数表的共享问题

    问题: 包含一个以上虚函数的 class B, 它所定义的 对象是否共用一个虚函数表? 分析: 由于含有虚函数,因此对象内存包含了一个指向虚函数表的指针,但是这个指针指向的是同一个虚函数表吗? 实验如 ...

  7. 对C++虚函数、虚函数表的简单理解

    一.虚函数的作用 以一个通用的图形类来了解虚函数的定义,代码如下: #include "stdafx.h" #include <iostream> using name ...

  8. 深入理解C++虚函数表

    虚函数表是C++类中存放虚函数的一张表,理解虚函数表对于理解多态很重要. 本次使用的编译器是VS2013,为了简化操作,不用去操作函数指针,我使用到了VS的CL编译选项来查看类的内存布局. CL使用方 ...

  9. C++虚函数与虚函数表

    多态性可分为两类:静态多态和动态多态.函数重载和运算符重载实现的多态属于静态多态,动态多态性是通过虚函数实现的. 每个含有虚函数的类有一张虚函数表(vtbl),表中每一项是一个虚函数的地址, 也就是说 ...

随机推荐

  1. tab栏切换的特殊效果

    在实际的开发过程中,我们可能会遇到这种需求,如下图 左边是三个tab栏,右边是显示内容的div,当鼠标滑到坐标的tab上时,给它一个高亮显示,让它对应的内容在右边的div中显示出来,当鼠标移出的时候把 ...

  2. 在rails中 Rendering Partials through Ajax

    之前做.net的时候,自己做了一个showcontent的插件,用来加载页面的局部partial 之前采用的是ashx的方式 但rails里面不太方面,今天找到一个比较好的方法,试验成功 起初网上找到 ...

  3. 开发前准备 va2015安装

    1.下载vs2015 2.进行安装(同时安装node.js.npm与Android SDK,会省很多时间) 安装的时候要选择自定义安装 如果先安装了Android SDK的话就不要勾选了,我就是勾选了 ...

  4. asp.net mvc 事件顺序

    1. OnActionExecuting 2. Before return View() 3. OnActionExecuted 4. OnResultExecuting 5. Hello from  ...

  5. 用 R 进行高频金融数据分析简介

    作者:李洪成 摘自:http://cos.name/wp-content/uploads/2013/11/ChinaR2013SH_Nov03_04_LiHongcheng.pdf 高频数据 金融市场 ...

  6. sqlserver和oracle的递归查询

    1.sqlserver递归查询方式 CTE: if OBJECT_ID('tb','N') is not null   drop table tb;     create table tb(id va ...

  7. 那年有关 return ; 的一切

    现在只知道在dev C++里面声明了函数的返回类型不为void就不能写return; ,但是如果返回值为void就可以写return; ,而且如你所愿.

  8. make基础(转)

    1. 基本规则 请点评 除了Hello World这种极简单的程序之外,一般的程序都是由多个源文件编译链接而成的,这些源文件的处理步骤通常用Makefile来管理.Makefile起什么作用呢?我们先 ...

  9. [转]Maven 划分模块

    所有用Maven管理的真实的项目都应该是分模块的,每个模块都对应着一个pom.xml.它们之间通过继承和聚合(也称作多模块,multi-module)相互关联.那么,为什么要这么做呢?我们明明在开发一 ...

  10. 3.一起来学hibernate之配置文件2

    之前的映射文件配置都是简单的.基础的配置,只涉及到单个javabean,对于单个javabean的增删改查都能很好的.简单的去完成. 但是知道简单配置远远不够,并不能完成很多复杂的情况,比如对象与对象 ...