C++——多态底层原理

虚函数表

先来看这个问题:

class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
private:
int _b = 1;
};

sizeof(Base)是多少? 

答案是:8

因为Base中除了成员变量_b,还有一个虚函数表_vfptr(当类中有虚函数就会生成),虚函数表的本质是函数指针数组,用来存储虚函数的指针


class Base
{
public:virtual void Func1(){cout << "Base::Func1()" << endl;}virtual void Func2(){cout << "Base::Func2()" << endl;}void Func3(){cout << "Base::Func3()" << endl;}
private:int _a = 1;
};class Derive :public Base 
{
public:void Func1(){cout << "Derive::Func1()" << endl;}
private:int _b = 2;
};void test1()
{Base bs;Derive de;
}

 

基类虚函数表

 

派生类基函数表

派生类虚函数表是这样生成的:先将基类的虚函数表内容拷贝过来,在把派生类中重写的虚函数的地址覆盖到被重写的虚函数地址上,最后将派生类自己增加的虚函数依次增加到虚表的末尾。

上面这个例子中,派生类和基类的虚函数表都存储有两个函数地址,即Func1和Func2,但是派生类只对Func1重写,没有对Func2重写,编译器会将派生类中的Func1的地址进行覆盖,所以Func1的地址不同,Func2的地址相同(0x009f1370)

注意:虚函数存在哪,虚函数表存在哪?

虚函数和普通的成员函数一样都存在公共代码段,虚函数表只是存了虚函数的指针;虚函数表存在哪,这个要看编译器,感兴趣可以自己验证。

我的编译器是将虚函数表存储在类对象的开始位置,使用32位系统虚表大小4byte

void test()
{int i = 0;printf("栈区:%p\n", &i);char* ch = new char;printf("堆区:%p\n", ch);static int ci = 1;printf("数据段:%p\n", &ci);const char* s = "hello";printf("代码段:%p\n", s);Derive de;printf("虚表:%p\n", *((int*)&de));}

虚表地址和代码段地址相差48byte,基本可以认为虚表存在代码段。大家可以参考这个方法,根据自己的编译器和环境来测试


多态原理

说了这么多,虚函数表在多态中有什么用?

当我们有基类的指针或引用调用派生类的虚函数时,第一步是将派生类赋值给基类,这时派生类只会将属于基类的那部分交给基类的指针或引用,而属于基类的那部分中包含了派生类的虚函数表。这样造成的结果就是,调用虚函数时调用了派生类中重写过的虚函数,实现了多态。

为什么直接将派生类赋值给基类就不能实现多态呢?因为这种写法会在编译器编译时,直接从符号表确定函数地址,程序运行时直接调用。而多态调用在编译期间不会确定,只在程序运行时到对象中寻找函数。其实编译器并不知道这个对象是基类还是派生类,只是无脑从虚函数表中找。


下面这个测试程序,通过观察汇编代码,就可以直观看出普通调用和多态调用的区别:

void test()
{Base bs;Derive de;bs = de;Base& b = de;bs.Func1();b.Func1();
}

多继承的虚函数表

前面都是以单继承为例讲虚函数表和多态原理,下面我们看多继承的虚函数表

class Base1
{
public:virtual void Func1(){cout << "Base1::Func1()" << endl;}virtual void Func2(){cout << "Base1::Func2()" << endl;}
private:int _a = 1;
};class Base2
{
public:virtual void Func3(){cout << "Base2::Func3()" << endl;}virtual void Func4(){cout << "Base2::Func4()" << endl;}
};
class Derive :public Base1 ,public Base2
{
public:void Func1(){cout << "Derive::Func1()" << endl;}void Func3(){cout << "Derive::Func3()" << endl;}virtual void Func5(){cout << "Derive::Func5()" << endl;}
private:int _b = 2;
};void test()
{Base1 b1;Base2  b2;Derive d;
}

调试: 

 

 

多继承后的派生类有两个虚函数表,Func2和Func4没有重写,与基类函数地址相同。Func1和Func3重写后进行覆盖。派生类新增加的虚函数地址存到了最先继承的基类Base1的虚表中(第一张图的监视窗口未显示,有bug,在内存窗口可以看到) 

重写是对虚函数函数体部分的重写

看下面程序的运行结果是什么:

 class A{public:virtual void func(int val = 1){ std::cout<<"A->"<< val <<std::endl;}virtual void test(){ func();}};class B : public A{public:void func(int val=0){ std::cout<<"B->"<< val <<std::endl; }};int main(int argc ,char* argv[]){B*p = new B;p->test();return 0;}

test函数没有重写,直接调用A::test();func函数被重写,虚表中是B中func的函数地址,又因为重写只是对函数体的重写,val缺省值是0,结果是B->0.

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

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

相关文章

【WinRAR】去除请购买WinRAR许可

新建rarreg.key文件 在WinRAR安装目录新建rarreg.key文件&#xff0c;文件内容如下: RAR registration datawncnUnlimited Company LicenseUID1b064ef8b57de3ae9b5264122122509b52e35fd885373b214a4a64cc2fc1284b77ed14fa2066ebfca6509f9813b32960fce6cb5ffde62890079861be57…

JDBC-day02(使用PreparedStatement实现CRUD操作)

所需的数据库数据要导入到自己的数据库库中 三&#xff1a;使用PreparedStatement实现CRUD操作 数据库连接被用于向数据库服务器发送命令和 SQL 语句&#xff0c;并接受数据库服务器返回的结果。其实一个数据库连接就是一个Socket连接。CRUD操作&#xff1a;根据返回值的有无…

HDLbits: Edgedetect

module top_module (input clk,input [7:0] in,output [7:0] pedge );reg [7:0] in_old;always(posedge clk)beginin_old < in; end assign pedge < in & ~in_old; endmodule 对于边缘检测而言&#xff0c;若是0→1和1→0都检测则为in^in_old&#xf…

java实验(头歌)--面向对象封装继承和多态

文章目录 第一题第二题第三题第四题第五题第六题第七题第八题 快速完成实验的方法&#xff1a; 把对应题目的主函数替换&#xff0c;其他复制粘贴。 第一题 public class TestPersonDemo {public static void main(String[] args) {/********* begin *********/// 声明并实例化…

练[FBCTF2019]RCEService

[FBCTF2019]RCEService 文章目录 [FBCTF2019]RCEService掌握知识解题思路关键paylaod 掌握知识 ​ json字符串格式&#xff0c;命令失效(修改环境变量)–绝对路径使用linux命令&#xff0c;%0a绕过preg_match函数&#xff0c;代码审计 解题思路 打开题目链接&#xff0c;发现…

2023年中国互联网本地生活服务行业发展历程及趋势分析:国内市场仍有增长潜力[图]

我国本地生活进入4.0时代&#xff0c;“附近消费”场景迭代、渠道多元&#xff1b;更多玩家涌入本地生活赛道&#xff0c;本地消费场景分散到多平台、多模式&#xff0c;线下门店短视频直播运营组合蔚然成风。 本地生活行业发展历程 资料来源&#xff1a;共研产业咨询&#xf…

光伏并网逆变器低电压穿越技术研究(Simulink仿真)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

R实现数据分布特征的视觉化——多笔数据之间的比较

大家好&#xff0c;我是带我去滑雪&#xff01; 如果要对两笔数据或者多笔数据的分布情况进行比较&#xff0c;Q-Q图、柱状图、星形图都是非常好的选择&#xff0c;下面开始实战。 &#xff08;1&#xff09;绘制Q-Q图 首先导入数据bankwage.csv文件&#xff0c;该数据集…

解决:docker安装nginx,在腾讯云服务器可以打开但在本地的电脑浏览器不能打开

第一步运行nginx&#xff1a; [rootVM-24-4-centos ~]# docker run -d --name nginx01 -p 8080:80 nginx [rootVM-24-4-centos ~]# curl localhost:8080 <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> html …

JMeter压测如何分配业务比例?

在进行综合场景压测时&#xff0c;由于不同的请求&#xff0c;要求所占比例不同&#xff0c;那如何实现呢&#xff1f; 有人说将这些请求分别放到单独的线程组下&#xff0c;然后将线程组的线程数按照比例进行配置&#xff0c;这种方法不是很好&#xff0c;想想&#xff0c;不…

关于 Vue-iClient-MapboxGL 的使用注意事项

官网&#xff1a;https://iclient.supermap.io/web/apis/vue/zh/api/guide/installation.html 关于图的使用&#xff0c;其余的引入步骤不再赘述&#xff0c;仅说注意事项。 推荐使用的是全局引入&#xff0c;也就是完整引入 因为单独引入我踩了不少坑&#xff0c;比如说 cs…

Thinking for Doing:让LLMs能推断他人心理状态来做出适当的行动。

LLMs通常能回答有关心理状态的问题&#xff0c;但往往不能将这些推断用于实际行动。例如&#xff0c;如果一个故事中的角色正在寻找他的背包&#xff0c;而模型知道背包在厨房里&#xff0c;那么模型应该能推断出最好的行动是建议角色去厨房查看。T4D 的目的就是要求模型不仅要…

Java学习day08:面向对象三大特性之一:继承

声明&#xff1a;该专栏本人重新过一遍java知识点时候的笔记汇总&#xff0c;主要是每天的知识点题解&#xff0c;算是让自己巩固复习&#xff0c;也希望能给初学的朋友们一点帮助&#xff0c;大佬们不喜勿喷(抱拳了老铁&#xff01;) Java学习day08&#xff1a;面向对象三大特…

辅助寄存器是干什么用的

目录 请问CPU 的 MREQ 引脚和 IORQ 引脚分别是干什么用的 那这里的引脚是什么含义呢&#xff1f; 程序是指令和数据的集合 辅助寄存器是干什么用的 寄存器的用途取决于它的类型 PC 寄存器也叫作“程序指针”&#xff0c;存储着指向 CPU 接下来 要执行的指令的地址。PC 寄存…

Suricata – 入侵检测、预防和安全工具

一、Suricata介绍 Suricata是一个功能强大、用途广泛的开源威胁检测引擎&#xff0c;提供入侵检测 (IDS)、入侵防御 (IPS) 和网络安全监控功能。它执行深度数据包&#xff08;网络流量&#xff09;检查以及模式匹配&#xff0c;在威胁检测中非常强大。 工作流程&#xff1a; 主…

关于对XSS原理分析与绕过总结

一、原理 该文章仅用于信息防御技术教学&#xff0c;请勿用于其他用途。 1、XSS原理 XSS&#xff08;跨站脚本攻击&#xff09;是一种常见的网络安全漏洞&#xff0c;攻击者通常会在网页中插入恶意的 JavaScript 代码。由于服务器对输入数据的过滤和验证不严格&#xff0c;这…

leetcode做题笔记160. 相交链表

给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#xff0c;函数返回结果后&…

【力扣每日一题】2023.10.7 股票价格跨度

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 给我们一个数组表示不同时间的股票的价格&#xff0c;要我们按照顺序返回每天的股票价格跨度&#xff0c;价格跨度就是股票价格小于或等于…

Java源码分析(三)ArrayList

ArrayList是我们经常用到的一个集合类&#xff0c;那么本篇我们一起学习下ArrayList的源码。 一、创建ArrayList 首先&#xff0c;我们从创建ArrayList开始。如下代码&#xff0c;创建一个空的ArrayList&#xff1a; List<String> list new ArrayList<>(); 看下…

从零开始开发企培源码:搭建企业内训小程序的全面指南

在当今竞争激烈的企业世界中&#xff0c;不断提升员工的技能和知识已经成为了保持竞争力的必要条件之一。因此&#xff0c;越来越多的企业开始重视内部培训&#xff0c;以确保员工具备所需的技能和知识。为了更有效地进行内部培训&#xff0c;许多企业都在考虑开发自己的企业内…