C++的虚函数表
这里的例子全部来自陈皓的C++ 虚函数表解析,经过修改的。
编译器:g++ (Ubuntu 4.9.2-10ubuntu13) 4.9.2
环境:ubuntu 15.04 64位系统(地址占8字节)
例子1:
#define LL long long class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
LL a;
}; int main(void)
{
typedef void (*Fun)(void);
Base b;
b.a=; Fun pFun = NULL; cout << "虚函数表地址:" << (int*)(&b) << endl;
cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl; LL *p=(LL*)*(LL*)&b;
for(int i=; i<; i++)
{
pFun = (Fun)*(p+i);
pFun();
}
cout<<"a的地址: "<<(&b)+<<endl;
cout<<"a的值: "<<*((LL*)(&b)+)<<endl;
cout<<"对象b的大小:"<<sizeof(b)<<endl; return ;
}
输出:
解释:通过强转对象b的地址,将地址逐个取出来运行看看是什么。
总结:对象b中存储了2个东西,第一个是虚函数表指针,第二个是变量a,所以共计8+8=16字节。在虚函数表指针所指的地址中,有3个指针,分别是3个虚函数的指针,每个占8字节,共计3*8=24字节。陈皓说虚表最后加个标志,这点不知道怎么验证~
验证了如下这副图:
例子2:
一般继承,无函数覆盖的,全部函数都声明为virtual:
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
}; class Derive:public Base{
public:
virtual void f1() { cout << "Derive::f1" << endl; }
virtual void g1() { cout << "Derive::g1" << endl; }
virtual void h1() { cout << "Derive::h1" << endl; }
}; int main(void)
{
typedef void (*Fun)(void);
Derive d;
Fun pFun = NULL; cout << "虚函数表地址:" << (LL*)(&d) << endl;
cout << "虚函数表 — 第一个函数地址:" << (LL*)*(LL*)(&d) << endl; LL *p=(LL*)*(LL*)&d;
for(int i=; i<; i++)
{
pFun = (Fun)*(p+i);
pFun();
}
cout<<"对象d的大小:"<<sizeof(d)<<endl; return ;
}
输出:
解释:仅仅只有2个类,且Derive继承Base类,一共有6个虚函数。
总结:对象d中一共占用8个字节,也就是只保存了虚函数表的地址。虚函数表中一共有6个函数指针,分别是Base的3个+Derive的3个。
验证了如下这副图:
例子3:
一般继承,只有1个函数是覆盖的,全部函数都声明为virtual:
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
}; class Derive:public Base{
public:
virtual void f() { cout << "Derive::f" << endl; }
virtual void g1() { cout << "Derive::g1" << endl; }
virtual void h1() { cout << "Derive::h1" << endl; }
}; int main(void)
{
typedef void (*Fun)(void);
Derive d;
Fun pFun = NULL; cout << "虚函数表地址:" << (LL*)(&d) << endl;
cout << "虚函数表 — 第一个函数地址:" << (LL*)*(LL*)(&d) << endl; LL *p=(LL*)*(LL*)&d;
for(int i=; i<; i++)
{
pFun = (Fun)*(p+i);
pFun();
} cout<<"对象d的大小:"<<sizeof(d)<<endl; return ;
}
输出:
解释:这个模型跟上面的差不多,而这次有1个函数是覆盖的。
总结:虚函数表中一共有5个函数指针,Base中占2个,Derive中占3个。流程是先将基类的3个函数摆在虚函数表前面,接着摆派生类的,由于Derive的函数f覆盖掉基类Base的函数f,所以直接代替基类的函数f的位置。多态是:用基类的指针指向派生类对象,调用的是有覆盖的派生类中的函数。这里就可以实现多态~
验证了如下这副图:
例子4:
多重继承,无虚函数覆盖,但全部函数都声明为虚函数。
class Base1 {
public:
virtual void f() { cout << "Base1::f" << endl; }
virtual void g() { cout << "Base1::g" << endl; }
virtual void h() { cout << "Base1::h" << endl; }
};
class Base2 {
public:
virtual void f() { cout << "Base2::f" << endl; }
virtual void g() { cout << "Base2::g" << endl; }
virtual void h() { cout << "Base2::h" << endl; }
};
class Base3 {
public:
virtual void f() { cout << "Base3::f" << endl; }
virtual void g() { cout << "Base3::g" << endl; }
virtual void h() { cout << "Base3::h" << endl; }
}; class Derive:public Base1,public Base2,public Base3{
public:
virtual void f1() { cout << "Derive::f1" << endl; }
virtual void g1() { cout << "Derive::g1" << endl; }
}; int main(void)
{
typedef void (*Fun)(void);
Derive d;
Fun pFun = NULL; cout << "虚函数表地址:" << (LL*)(&d) << endl;
cout << "虚函数表 — 第一个函数地址:" << (LL*)*(LL*)(&d) << endl; LL *p=(LL*)*(LL*)&d;
for(int i=; i<; i++)
{
pFun = (Fun)*(p+i);
pFun();
}
for(int i=; i<; i++)
{
pFun = (Fun)*(p+i);
pFun();
}
for(int i=; i<; i++)
{
pFun = (Fun)*(p+i);
pFun();
} cout<<"对象d的大小:"<<sizeof(d)<<endl; return ;
}
输出:
解释:Derive类一共有3个基类,按1,2,3的顺序继承。由于有3个基类,所以对象d中有且仅有3个指针,分别指向其基类的虚函数表。
总结:虚函数表1中有5个函数,虚函数表2和3中有3个函数。也就是说,基类Derive中的虚函数是借放在第一个基类的虚函数表中的尾部。
验证了如下这副图:
例子5:
多重继承,有1个虚函数覆盖,全部函数声明为虚函数。
class Base1 {
public:
virtual void f() { cout << "Base1::f" << endl; }
virtual void g() { cout << "Base1::g" << endl; }
virtual void h() { cout << "Base1::h" << endl; }
};
class Base2 {
public:
virtual void f() { cout << "Base2::f" << endl; }
virtual void g() { cout << "Base2::g" << endl; }
virtual void h() { cout << "Base2::h" << endl; }
};
class Base3 {
public:
virtual void f() { cout << "Base3::f" << endl; }
virtual void g() { cout << "Base3::g" << endl; }
virtual void h() { cout << "Base3::h" << endl; }
}; class Derive:public Base1,public Base2,public Base3{
public:
virtual void f() { cout << "Derive::f" << endl; }
virtual void g1() { cout << "Derive::g1" << endl; }
}; int main(void)
{
typedef void (*Fun)(void);
Derive d;
Fun pFun = NULL; cout << "虚函数表地址:" << (LL*)(&d) << endl;
cout << "虚函数表 — 第一个函数地址:" << (LL*)*(LL*)(&d) << endl; LL *p=(LL*)*(LL*)&d;
for(int i=; i<; i++)
{
pFun = (Fun)*(p+i);
pFun();
}
for(int i=; i<; i++)
{
pFun = (Fun)*(p+i);
pFun();
}
for(int i=; i<; i++)
{
pFun = (Fun)*(p+i);
pFun();
} cout<<"对象d的大小:"<<sizeof(d)<<endl; return ;
}
输出:
解释:例子基本和上一个例子一样。基类Derive中的虚函数f 覆盖掉3个基类中的同名虚函数f。
总结:依然是3个函数表,只是每个函数表中的同名函数f 都被替换成了基类Derive中的函数f 。所以虚函数表1中有4个指针,表2和3都分别有3个。
C++的虚函数表的更多相关文章
- C++ 虚函数表解析
转载:陈皓 http://blog.csdn.net/haoel 前言 C++中 的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实 ...
- C++ 多态、虚函数机制以及虚函数表
1.非virtual函数,调用规则取决于对象的显式类型.例如 A* a = new B(); a->display(); 调用的就是A类中定义的display().和对象本体是B无关系. 2. ...
- C++迟后联编和虚函数表
先看一个题目: class Base { public: virtual void Show(int x) { cout << "In Base class, int x = & ...
- C++ 知道虚函数表的存在
今天翻看陈皓大大的博客,直接找关于C++的东东,看到了虚函数表的内容,找一些能看得懂的地方记下笔记. 0 引子 类中存在虚函数,就会存在虚函数表,在vs2015的实现中,它存在于类的头部. 假设有如下 ...
- C++虚函数和虚函数表
前导 在上面的博文中描述了基类中存在虚函数时,基类和派生类中虚函数表的结构. 在派生类也定义了虚函数时,函数表又是怎样的结构呢? 先看下面的示例代码: #include <iostream> ...
- C++ Daily 《5》----虚函数表的共享问题
问题: 包含一个以上虚函数的 class B, 它所定义的 对象是否共用一个虚函数表? 分析: 由于含有虚函数,因此对象内存包含了一个指向虚函数表的指针,但是这个指针指向的是同一个虚函数表吗? 实验如 ...
- C++虚函数表
大家知道虚函数是通过一张虚函数表来实现的.在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承.覆盖的问题,其内容真是反应实际的函数.这样,在有虚函数的类的实例中,这个表分配在了这个实例的内存中 ...
- 对C++虚函数、虚函数表的简单理解
一.虚函数的作用 以一个通用的图形类来了解虚函数的定义,代码如下: #include "stdafx.h" #include <iostream> using name ...
- 深入理解C++虚函数表
虚函数表是C++类中存放虚函数的一张表,理解虚函数表对于理解多态很重要. 本次使用的编译器是VS2013,为了简化操作,不用去操作函数指针,我使用到了VS的CL编译选项来查看类的内存布局. CL使用方 ...
- C++虚函数与虚函数表
多态性可分为两类:静态多态和动态多态.函数重载和运算符重载实现的多态属于静态多态,动态多态性是通过虚函数实现的. 每个含有虚函数的类有一张虚函数表(vtbl),表中每一项是一个虚函数的地址, 也就是说 ...
随机推荐
- VS输入输出基本操作以及数据类型和类型转换
(一) C#项目的组成结构 项目结构 .config ---配置文件(存放配置参数文件) .csproj ---项目文件(管理文件项) .sln ---解决方案文件(管理项目) .cs - ...
- c# form的设定
1. 窗体的显示位置startPosition CenterScreen 窗体在当前居中 CenterParent 窗体在其父窗体居中 WindowDefaultBounds 窗体定期在windows ...
- Caffe 深度学习框架介绍
转自:http://suanfazu.com/t/caffe/281 Caffe是一个清晰而高效的深度学习框架,其作者是博士毕业于UC Berkeley的贾扬清,目前在Google工作. Caffe是 ...
- 时间序列数据库选型——本质是列存储,B-tree索引,抑或是搜索引擎中的倒排索引
时间序列数据库最多,使用也最广泛.一般人们谈论时间序列数据库的时候指代的就是这一类存储.按照底层技术不同可以划分为三类. 直接基于文件的简单存储:RRD Tool,Graphite Whisper.这 ...
- c++ boost 汉字和模式串混用的例子
*=============================================================== * Copyright (C) All rights reserved ...
- dll和ocx比较
ActiveX,OLE是基于COM的一种应用,其文件后缀一般以dll和ocx结尾:ocx作为一种特殊的dll文件,具有一定的用户界面和事件响应,而dll文件只是方法和属性的集合. 一.关于DLL的介绍 ...
- [整理]Linux压缩与解压缩命令整理。
一.压缩文件命令 1.*.Z compress 程序压缩的档案:2.*.bz2 bzip2 程序压缩的档案:3.*.gz gzip 程序压缩的档案:4.*.tar tar 程序打包的数据,并没有压缩过 ...
- Hive的Transform功能
Hive的TRANSFORM关键字提供了在SQL中调用自写脚本的功能,适合实现Hive中没有的功能又不想写UDF的情况.例如,按日期统计每天出现的uid数,通常用如下的SQL SELECT date, ...
- [示例]NSDictionary编程题-字典的排序应用(iOS4班)
代码: #import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepo ...
- <input type="hidden" id="haha" name="wang" value="xiaodong" />
jsp中一个隐藏的文本框,文本框里的值是:xiaodong id属性和name属性:就是在JavaScript中或者控制器中根据id或name属性取它的value的值 开发人员所需要,又不想让用户看到 ...