C++的虚函数表

这里的例子全部来自陈皓的C++ 虚函数表解析,经过修改的。

编译器:g++ (Ubuntu 4.9.2-10ubuntu13) 4.9.2

环境:ubuntu 15.04  64位系统(地址占8字节)

 

例子1:

 1 #define LL long long
 2 
 3 class Base {
 4 public:
 5     virtual void f() { cout << "Base::f" << endl; }
 6     virtual void g() { cout << "Base::g" << endl; }
 7     virtual void h() { cout << "Base::h" << endl; }
 8     LL a;    
 9 };
10 
11 int main(void)
12 {
13     typedef void (*Fun)(void);
14     Base b;
15     b.a=10086;
16 
17     Fun pFun = NULL;
18 
19     cout << "虚函数表地址:" << (int*)(&b) << endl;
20     cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl;
21 
22     LL *p=(LL*)*(LL*)&b;
23     for(int i=0; i<3; i++)
24     {
25         pFun = (Fun)*(p+i);
26         pFun();
27     }
28     cout<<"a的地址: "<<(&b)+1<<endl;
29     cout<<"a的值: "<<*((LL*)(&b)+1)<<endl;
30     cout<<"对象b的大小:"<<sizeof(b)<<endl;    
31 
32     return 0;
33 }

输出:

 

  解释:通过强转对象b的地址,将地址逐个取出来运行看看是什么。

  总结:对象b中存储了2个东西,第一个是虚函数表指针,第二个是变量a,所以共计8+8=16字节。在虚函数表指针所指的地址中,有3个指针,分别是3个虚函数的指针,每个占8字节,共计3*8=24字节。陈皓说虚表最后加个标志,这点不知道怎么验证~

  验证了如下这副图:

  

 

 

 

例子2:

  一般继承,无函数覆盖的,全部函数都声明为virtual:  

  

 1 class Base {
 2 public:
 3     virtual void f() { cout << "Base::f" << endl; }
 4     virtual void g() { cout << "Base::g" << endl; }
 5     virtual void h() { cout << "Base::h" << endl; }    
 6 };
 7 
 8 class Derive:public Base{
 9 public:
10     virtual void f1() { cout << "Derive::f1" << endl; }
11     virtual void g1() { cout << "Derive::g1" << endl; }
12     virtual void h1() { cout << "Derive::h1" << endl; }    
13 };
14 
15 int main(void)
16 {
17     typedef void (*Fun)(void);
18     Derive d;
19     Fun pFun = NULL;
20 
21     cout << "虚函数表地址:" << (LL*)(&d) << endl;
22     cout << "虚函数表 — 第一个函数地址:" << (LL*)*(LL*)(&d) << endl;
23 
24     LL *p=(LL*)*(LL*)&d;
25     for(int i=0; i<6; i++)
26     {
27         pFun = (Fun)*(p+i);
28         pFun();
29     }
30     cout<<"对象d的大小:"<<sizeof(d)<<endl;    
31 
32     return 0;
33 }

输出:

  解释:仅仅只有2个类,且Derive继承Base类,一共有6个虚函数。

  总结:对象d中一共占用8个字节,也就是只保存了虚函数表的地址。虚函数表中一共有6个函数指针,分别是Base的3个+Derive的3个。

  验证了如下这副图:

  

 

例子3:

  一般继承,只有1个函数是覆盖的,全部函数都声明为virtual:
  

 1 class Base {
 2 public:
 3     virtual void f() { cout << "Base::f" << endl; }
 4     virtual void g() { cout << "Base::g" << endl; }
 5     virtual void h() { cout << "Base::h" << endl; }    
 6 };
 7 
 8 class Derive:public Base{
 9 public:
10     virtual void f() { cout << "Derive::f" << endl; }
11     virtual void g1() { cout << "Derive::g1" << endl; }
12     virtual void h1() { cout << "Derive::h1" << endl; }    
13 };
14 
15 int main(void)
16 {
17     typedef void (*Fun)(void);
18     Derive d;
19     Fun pFun = NULL;
20 
21     cout << "虚函数表地址:" << (LL*)(&d) << endl;
22     cout << "虚函数表 — 第一个函数地址:" << (LL*)*(LL*)(&d) << endl;
23 
24     LL *p=(LL*)*(LL*)&d;
25     for(int i=0; i<5; i++)
26     {
27         pFun = (Fun)*(p+i);
28         pFun();
29     }
30 
31     cout<<"对象d的大小:"<<sizeof(d)<<endl;    
32      
33     return 0;
34 }

输出:

  解释:这个模型跟上面的差不多,而这次有1个函数是覆盖的。

  总结:虚函数表中一共有5个函数指针,Base中占2个,Derive中占3个。流程是先将基类的3个函数摆在虚函数表前面,接着摆派生类的,由于Derive的函数f覆盖掉基类Base的函数f,所以直接代替基类的函数f的位置。多态是:用基类的指针指向派生类对象,调用的是有覆盖的派生类中的函数。这里就可以实现多态~

  验证了如下这副图:

  

 

 

例子4:

  多重继承,无虚函数覆盖,但全部函数都声明为虚函数。

   

 1 class Base1 {
 2 public:
 3     virtual void f() { cout << "Base1::f" << endl; }
 4     virtual void g() { cout << "Base1::g" << endl; }
 5     virtual void h() { cout << "Base1::h" << endl; }    
 6 };
 7 class Base2 {
 8 public:
 9     virtual void f() { cout << "Base2::f" << endl; }
10     virtual void g() { cout << "Base2::g" << endl; }
11     virtual void h() { cout << "Base2::h" << endl; }    
12 };
13 class Base3 {
14 public:
15     virtual void f() { cout << "Base3::f" << endl; }
16     virtual void g() { cout << "Base3::g" << endl; }
17     virtual void h() { cout << "Base3::h" << endl; }    
18 };
19 
20 class Derive:public Base1,public Base2,public Base3{
21 public:
22     virtual void f1() { cout << "Derive::f1" << endl; }
23     virtual void g1() { cout << "Derive::g1" << endl; }
24 };
25  
26 int main(void)
27 {
28     typedef void (*Fun)(void);
29     Derive d;
30     Fun pFun = NULL;
31 
32     cout << "虚函数表地址:" << (LL*)(&d) << endl;
33     cout << "虚函数表 — 第一个函数地址:" << (LL*)*(LL*)(&d) << endl;
34 
35     LL *p=(LL*)*(LL*)&d;
36     for(int i=0; i<5; i++)
37     {
38         pFun = (Fun)*(p+i);
39         pFun();
40     }
41     for(int i=0; i<3; i++)
42     {
43         pFun = (Fun)*(p+i);
44         pFun();
45     }
46     for(int i=0; i<3; i++)
47     {
48         pFun = (Fun)*(p+i);
49         pFun();
50     }
51 
52     cout<<"对象d的大小:"<<sizeof(d)<<endl;    
53      
54     return 0;
55 }

输出:

  解释:Derive类一共有3个基类,按1,2,3的顺序继承。由于有3个基类,所以对象d中有且仅有3个指针,分别指向其基类的虚函数表。

  总结:虚函数表1中有5个函数,虚函数表2和3中有3个函数。也就是说,基类Derive中的虚函数是借放在第一个基类的虚函数表中的尾部。

  验证了如下这副图:

  

 

例子5:

  多重继承,有1个虚函数覆盖,全部函数声明为虚函数。

  

 1 class Base1 {
 2 public:
 3     virtual void f() { cout << "Base1::f" << endl; }
 4     virtual void g() { cout << "Base1::g" << endl; }
 5     virtual void h() { cout << "Base1::h" << endl; }    
 6 };
 7 class Base2 {
 8 public:
 9     virtual void f() { cout << "Base2::f" << endl; }
10     virtual void g() { cout << "Base2::g" << endl; }
11     virtual void h() { cout << "Base2::h" << endl; }    
12 };
13 class Base3 {
14 public:
15     virtual void f() { cout << "Base3::f" << endl; }
16     virtual void g() { cout << "Base3::g" << endl; }
17     virtual void h() { cout << "Base3::h" << endl; }    
18 };
19 
20 class Derive:public Base1,public Base2,public Base3{
21 public:
22     virtual void f() { cout << "Derive::f" << endl; }
23     virtual void g1() { cout << "Derive::g1" << endl; }
24 };
25 
26 
27  
28 int main(void)
29 {
30     typedef void (*Fun)(void);
31     Derive d;
32     Fun pFun = NULL;
33 
34     cout << "虚函数表地址:" << (LL*)(&d) << endl;
35     cout << "虚函数表 — 第一个函数地址:" << (LL*)*(LL*)(&d) << endl;
36 
37     LL *p=(LL*)*(LL*)&d;
38     for(int i=0; i<4; i++)
39     {
40         pFun = (Fun)*(p+i);
41         pFun();
42     }
43     for(int i=0; i<3; i++)
44     {
45         pFun = (Fun)*(p+i);
46         pFun();
47     }
48     for(int i=0; i<3; i++)
49     {
50         pFun = (Fun)*(p+i);
51         pFun();
52     }
53 
54     cout<<"对象d的大小:"<<sizeof(d)<<endl;    
55      
56     return 0;
57 }

输出:

  解释:例子基本和上一个例子一样。基类Derive中的虚函数f 覆盖掉3个基类中的同名虚函数f。

  总结:依然是3个函数表,只是每个函数表中的同名函数f 都被替换成了基类Derive中的函数f 。所以虚函数表1中有4个指针,表2和3都分别有3个。

 

转载于:https://www.cnblogs.com/xcw0754/p/4973532.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/471410.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Hadoop学习笔记—10.Shuffle过程那点事儿

Hadoop学习笔记—10.Shuffle过程那点事儿 一、回顾Reduce阶段三大步骤 在第四篇博文《初识MapReduce》中&#xff0c;我们认识了MapReduce的八大步骤&#xff0c;其中在Reduce阶段总共三个步骤&#xff0c;如下图所示&#xff1a; 其中&#xff0c;Step2.1就是一个Shuffle操作&…

已安装Anaconda情况下,命令行pip,python报错(详细 已解决)

已安装Anaconda情况下&#xff0c;命令行pip,python报错&#xff08;已解决&#xff09; 这是报错截图 解决方案如下&#xff1a; 1.首先可以去找到anaconda文件夹&#xff0c;并打开该文件目录下的Script文件夹&#xff0c;查看是否有pip.exe文件&#xff0c;并查看是否配置…

LeetCode 2105. 给植物浇水 II(双指针)

文章目录1. 题目2. 解题1. 题目 Alice 和 Bob 打算给花园里的 n 株植物浇水。 植物排成一行&#xff0c;从左到右进行标记&#xff0c;编号从 0 到 n - 1 。其中&#xff0c;第 i 株植物的位置是 x i 。 每一株植物都需要浇特定量的水。 Alice 和 Bob 每人有一个水罐&#x…

通过Cisco packet Tracer 划分vlan实现不同交换机间pc进行通信

通过Cisco packet Tracer 划分vlan实现不同交换机间pc进行通信 交换机端口类型介绍&#xff1a; vlan 1.命令行实现将端口 fa0/4 fa0/5 fa0/6分配到vlan3: 2.命令行实现两个交换机模式由access切换为trunk,使得不同交换机中相同vlan中的PC能够进行通信

linux xp镜像文件,让Windows XP镜像文件小一点儿(转)

让Windows XP镜像文件小一点儿(转)[more]近日笔者的一位朋友遇上件烦心事儿&#xff0c;他发现安装好Windows XP Professional和Services Pack 1a补丁升级文件后&#xff0c;其文件大小总和已达到1.7GB容量。而用Ghost备份后的lmage镜像文件也有1.2GB之大&#xff0c;不能将它储…

Packet Tracer配置交换机详细步骤(实验)

1.首先建立如下图所示的网络拓扑结构 这里需要注意 PC 与 交换机switch交换机之间的连线方式&#xff1a; 1&#xff09;连线方式选择 控制台连线 2&#xff09;switch 中需要选择 console接口&#xff1b; 3&#xff09;PC 中需要选择 RS 232接口 2.点击PC -> 选择桌面 -…

LeetCode 2108. 找出数组中的第一个回文字符串

文章目录1. 题目2. 解题1. 题目 给你一个字符串数组 words &#xff0c;找出并返回数组中的 第一个回文字符串 。如果不存在满足要求的字符串&#xff0c;返回一个 空字符串 “” 。 回文字符串 的定义为&#xff1a;如果一个字符串正着读和反着读一样&#xff0c;那么该字符…

Eclipse用法和技巧二十三:查看JDK源码

使用java开发&#xff0c;如果能阅读JDK的经典代码&#xff0c;对自己的水平提高是很有帮助的。笔者在实际工作中总结了两种阅读JDK源码的方式。第一种下载android源代码&#xff0c;直接在android源码代码中&#xff0c;这里的代码虽然不是标准的JDK源码&#xff0c;但是也很不…

pip install 时报错 ERROR: Could not install packages due to an EnvironmentError: [WinError 5] 拒绝访问(已解决)

pip install 时报错 ERROR: Could not install packages due to an EnvironmentError: [WinError 5] 拒绝访问&#xff08;已解决&#xff09; 这是报错截图&#xff1a; 解决方案如下&#xff1a; 在pip install 后面加上 --user 需要安装的包名 例如&#xff1a; pip inst…

linux取消中文网,SELinux如何关闭

SELinux如何关闭1、临时关闭SELinuxsetenforce 0 ##设置SELinux 成为permissive模式##setenforce 1 设置SELinux 成为enforcing模式2、永久关闭SELinux修改/etc/selinux/config 文件将SELINUXenforcing改为SELINUXdisabled&#xff0c;重启机器。3、查看SELinux状态◆ /usr/sbi…

LeetCode 2109. 向字符串添加空格

文章目录1. 题目2. 解题1. 题目 给你一个下标从 0 开始的字符串 s &#xff0c;以及一个下标从 0 开始的整数数组 spaces 。 数组 spaces 描述原字符串中需要添加空格的下标。每个空格都应该插入到给定索引处的字符值 之前 。 例如&#xff0c;s "EnjoyYourCoffee&quo…

Packet Tracer 通过配置静态路由实现不同网段之间的通信(详细步骤)

Packet Tracer 通过配置静态路由实现不同网段之间的通信&#xff08;详细步骤&#xff09; 一、目标&#xff1a;ping通四个网段 二、解决方法&#xff1a;配置静态路由 1.拓扑结构布线图如下所示&#xff1a; 注意&#xff1a;这里省略了为每台PC配置IP地址 和 相对应的默认…

LeetCode 2110. 股票平滑下跌阶段的数目(滑动窗口)

文章目录1. 题目2. 解题1. 题目 给你一个整数数组 prices &#xff0c;表示一支股票的历史每日股价&#xff0c;其中 prices[i] 是这支股票第 i 天的价格。 一个 平滑下降的阶段 定义为&#xff1a;对于 连续一天或者多天 &#xff0c;每日股价都比 前一日股价恰好少 1 &…

JavaScript + Audio API自制简易音乐播放器(详细完整版、小白都能看懂)

JavaScript Audio API自制简易音乐播放器&#xff08;详细完整版&#xff09; ** 音乐播放器的功能清单如下&#xff1a; ** 1.点击暂停按钮&#xff0c;歌曲暂停 2.点击播放按钮&#xff0c;歌曲播放 3.单曲循环与取消单曲循环 4.当播放到列表最后一首歌曲时&#xff0c;点…

jQuery实现PC端商城购物车模块基本功能(每个商品的小计和合计都会根据添加和删除的操作来动态计算)

jQuery实现PC端商城购物车模块基本功能 先上效果图&#xff1a; 因为主要是想练习jQuery的使用&#xff0c;所以页面CSS部分比较简陋&#xff0c;有需要的话&#xff0c;大家在参考代码时&#xff0c;可以自己再完善下CSS部分的代码&#xff0c;让购物车页面更加美观。 功能…

七、CSS 三大特性(完整详细解析)

** CSS 三大特性&#xff08;完整详细解析&#xff09; ** 1.优先级&#xff1a; 定义CSS样式时&#xff0c;经常出现两个或更多规则应用在同一元素上&#xff0c;此时&#xff0c; 选择器相同&#xff0c;则执行层叠性选择器不同&#xff0c;就会出现优先级的问题。 1&am…

LeetCode 2114. 句子中的最多单词数

文章目录1. 题目2. 解题1. 题目 一个 句子 由一些 单词 以及它们之间的单个空格组成&#xff0c;句子的开头和结尾不会有多余空格。 给你一个字符串数组 sentences &#xff0c;其中 sentences[i] 表示单个 句子 。 请你返回单个句子里 单词的最多数目 。 示例 1&#xff1…

一、css清除浮动方法学习笔记总结(超详细,简单易懂)

** css清除浮动方法学习笔记总结&#xff08;超详细&#xff0c;简单易懂&#xff09; ** 问题&#xff1a; 上图中&#xff0c;由于container(父级元素&#xff09;未设置高度&#xff0c;其内部子元素设置了float浮动&#xff0c;导致与container同级&#xff08;也就是co…

LeetCode 2115. 从给定原材料中找到所有可以做出的菜(拓扑排序)

文章目录1. 题目2. 解题1. 题目 你有 n 道不同菜的信息。给你一个字符串数组 recipes 和一个二维字符串数组 ingredients 。 第 i 道菜的名字为 recipes[i] &#xff0c;如果你有它 所有 的原材料 ingredients[i] &#xff0c;那么你可以 做出 这道菜。一道菜的原材料可能是 另…

C语言makefile文件详解,makefile讲解

仅供自己学习使用一、Makefile介绍Makefile 或 makefile: 告诉make维护一个大型程序&#xff0c; 该做什么。Makefile说明了组成程序的各模块间的相互 关系及更新模块时必须进行的动作&#xff0c; make按照这些说明自动地维护这些模块。执行make命令时&#xff0c;需要一个 Ma…