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

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. python第二天基础1-1

    一.作用域 对于变量的作用域,执行声明并在内存中存在,该变量就可以在下面的代码中使用. if 1==1: name = 'wupeiqi' print name 二.三元运算 result = 值1  ...

  2. sys.dm_tran_locks,

    sys.dm_tran_locks 返回有关当前活动的锁管理器资源的信息.向锁管理器发出的已授予锁或正等待授予锁的每个当前活动请求分别对应一行. 列名 数据类型 说明 resource_type nv ...

  3. 学习maven的使用,看到一篇很实用的入门教程(菜鸟级入门)

    一.前言         早就知道maven 在java 项目的管理方面名声显赫,于是就想着学习掌握之,于是查阅了大量文档.发现这些文档的作者都是java 的大腕,大多都是站在掌握了一定maven 基 ...

  4. Alpha、Beta、RC、GA版本的区别 ZT

    http://www.blogjava.net/RomulusW/archive/2008/05/04/197985.html Alpha:是内部测试版,一般不向外部发布,会有很多Bug.一般只有测试 ...

  5. linux下配置ssledge代理服务器

    ssl edge 是一个非常好用的VPN/proxy, 比云梯 稳定快速的多.  在LINUX下开发 Titanium 需要用到各种FQ,所以它是必备工具. 1. 根据自己付费后的用户名和密码,下载 ...

  6. libev安装与示例程序编译运行

    Linux平台C网络编程,之前总是看各大名著(如UNIX环境高级编程和UNIX网络编程,还有TCP/IP详解 卷1:协议和深入理解计算机系统(原书第2版)),同时写点小程序练习.然而还是拿不出手. 参 ...

  7. android 连接蓝牙扫码枪,程序崩溃之onConfigurationChanged

    当android手机通过蓝牙连接扫码枪时,程序崩溃的原因之一是:键盘弹出或隐藏,触发程序走了onDestory->onCreate的生命周期,从而可能使得页面的某些初始化数据被清除了. 解决方法 ...

  8. http://blog.sina.com.cn/s/blog_4c3b6a070100etad.html

    http://blog.sina.com.cn/s/blog_4c3b6a070100etad.html

  9. libnode 0.4.0 发布,C++ 语言版的 Node.js

    libnode 0.4.0 支持 Windows ,提升了性能,libuv 更新到 0.10.17 版本,libj 更新到 0.8.2 版本. libnode 是 C++ 语言版的 Node.js,和 ...

  10. angularjs http 请求拦截器

    /** * Created by oy on 2016/11/29. */ (function() { 'use strict'; // 创建angular模块 angular .module('ap ...