【C++ 学习 ⑲】- 多态(下)

目录

一、虚函数表和多态的原理

1.1 - 虚函数表

1.2 - 多态的原理

二、单继承和多继承关系中的虚函数表

2.1 - 单继承关系中的虚函数表

2.2 - 多继承关系中的虚函数表

三、纯虚函数和抽象类



一、虚函数表和多态的原理

1.1 - 虚函数表

  1. 问:sizeof(b) 是多少?

    #include <iostream>
    using namespace std;
    ​
    class Base
    {
    public:virtual void func1() { cout << "Base::func1()" << endl; }virtual void func2() { cout << "Base::func2()" << endl; }void func3() { cout << "Base::func3()" << endl; }
    protected:int _i = 1;
    };
    ​
    int main()
    {Base b;cout << sizeof(b) << endl;return 0;
    }

    通过调试可以发现,在 b 对象内存模型中,除了 _i 成员,还有一个名为 _vfptr 的成员,它是虚函数表指针,所以 sizeof(b) 是 8 或 16 字节

    一个含有虚函数的类对象至少有一个指向虚函数表的指针,虚函数表本质上是一个存放虚函数地址的函数指针数组,一般情况下在这个数组的最后面还放了一个 nullptr

    虚函数表可以简称为虚表。因为 func3 不是虚函数,所以没有放进虚表中。

  2. 提一个很容易混淆的问题:虚函数存在哪里?虚表又存在哪里?虚函数和普通函数一样,都是存在代码段的;而虚表存在哪里可以通过以下代码得知

    #include <iostream>
    using namespace std;
    ​
    class Base
    {
    public:virtual void func1() { cout << "Base::func1()" << endl; }virtual void func2() { cout << "Base::func2()" << endl; }void func3() { cout << "Base::func3()" << endl; }
    protected:int _i = 1;
    };
    ​
    typedef void(*VFPTR)();
    ​
    int main()
    {int m = 0;printf("栈:%p\n", &m);
    ​int* p1 = new int;printf("堆:%p\n", p1);
    ​static int n = 0;printf("静态区:%p\n", &n);
    ​const char* str = "abcdef";printf("常量区:%p\n", str);
    ​Base b;// 通过对象的地址获取虚函数表的地址VFPTR* p2 = (VFPTR*)*(int*)&b;  printf("虚表:%p\n", p2);return 0;
    }

    根据输出结果,我们有理由相信虚表也是存在代码段的

  3. 让派生类 Derive 继承自 Base,然后在派生类中重写基类虚函数 func1:

    #include <iostream>
    using namespace std;
    ​
    class Base
    {
    public:virtual void func1() { cout << "Base::func1()" << endl; }virtual void func2() { cout << "Base::func2()" << endl; }void func3() { cout << "Base::func3()" << endl; }
    protected:int _i = 1;
    };
    ​
    class Derive : public Base
    {
    public:virtual void func1() { cout << "Derived::func1()" << endl; }
    protected:int _j = 2;
    };
    ​
    int main()
    {Base b;Derive d;return 0;
    }

    因为在派生类中重写了基类的虚函数 func1,所以基类对象 b 和派生类对象 d 的虚表是不一样的

    d 的虚表中存的是重写的 Derive::func1,所以虚函数的重写也叫作覆盖,覆盖就是虚表中虚函数的覆盖。重写是语法上的叫法,覆盖是原理层的叫法

1.2 - 多态的原理

#include <iostream>
using namespace std;
​
class Base
{
public:virtual void func1() { cout << "Base::func1()" << endl; }virtual void func2() { cout << "Base::func2()" << endl; }void func3() { cout << "Base::func3()" << endl; }
protected:int _i = 1;
};
​
class Derive : public Base
{
public:virtual void func1() { cout << "Derive::func1()" << endl; }
protected:int _j = 2;
};
​
int main()
{Base b;Base* pb = &b;pb->func1();  // Base::func1()
​Derive d;pb = &d;pb->func1();  // Derive::func1()return 0;
}

当基类指针 pb 指向基类对象 b 时,pb->func1(); 就是在 b 的虚表中找到虚函数 Base::func1

当基类指针 pb 指向派生类对象 d 时,pb->func1(); 就是在 d 的虚表中找到虚函数 Derive::func1

这样就让基类指针表现出了多种形态

注意:不满足多态的函数调用是编译时确定好的,满足多态的函数调用是运行时去对象中找的

Base b;
b.func1();
// 00195182 lea     ecx,[b]
// 00195185 call    Person::func1 (01914F6h)
// 汇编代码分析:
// 虽然 func1 是虚函数,但是 b 是对象,不满足多态的条件,所以这里是普通函数的调用,
// 编译时就确定好了函数的地址,直接 call。
​
Base* pb = &b;
pb->func1();
// 注意:不相关的汇编代码被省去了
// 001940DE mov     eax,dword ptr [pb]
// 001940E1 mov     edx,dword ptr [eax]
// 00B823EE mov     eax,dword ptr [edx]
// 001940EA call    eax
// 汇编代码分析:
// 1、pb 中存的是 b 对象的地址,将 pb 移动到 eax 中
// 2、[eax] 就是取 eax 值指向的内容,相当于把 b 对象中的虚表指针移动到 edx
// 3、[edx] 就是取 edx 值指向的内容,相当于把虚表中第一个虚函数的地址移动到 eax
// 4、call eax 中存的虚函数地址
// 由此可以看出满足多态的函数调用,不是在编译时确定的,而是运行起来后去对象中找的。

 


二、单继承和多继承关系中的虚函数表

2.1 - 单继承关系中的虚函数表

#include <iostream>
using namespace std;
​
class Base
{
public:virtual void func1() { cout << "Base::func1()" << endl; }virtual void func2() { cout << "Base::func2()" << endl; }
protected:int _i = 1;
};
​
class Derive : public Base
{
public:virtual void func1() { cout << "Derive::func1()" << endl; }virtual void func3() { cout << "Derive::func3()" << endl; }virtual void func4() { cout << "Derive::func4()" << endl; }
protected:int _j = 2;
};
​
int main()
{Base b;Derive d;return 0;
}

在 d 的虚表中,我们看不到虚函数 func3 和 func4,这可能是监视窗口故意隐藏了这两个函数,也可能是一个小 bug,我们可以通过以下代码进行验证

typedef void(*VFPTR)();
​
void PrintVftable(VFPTR vftable[])
{for (size_t i = 0; vftable[i] != nullptr; ++i){printf("第 %d 个虚函数地址:0X%p, -->", i, vftable[i]);vftable[i]();}cout << endl;
}
​
void test()
{Base b;VFPTR* p1 = (VFPTR*)*(int*)&b;PrintVftable(p1);
​Derive d;VFPTR* p2 = (VFPTR*)*(int*)&d;PrintVftable(p2);
}

 

2.2 - 多继承关系中的虚函数表

#include <iostream>
using namespace std;
​
class Base1
{
public:virtual void func1() { cout << "Base1::func1()" << endl; }virtual void func2() { cout << "Base1::func2()" << endl; }
protected:int _i1;
};
​
class Base2
{
public:virtual void func1() { cout << "Base2::func1()" << endl; }virtual void func2() { cout << "Base2::func2()" << endl; }
protected:int _i2;
};
​
class Derive : public Base1, public Base2
{
public:virtual void func1() { cout << "Derive::func1()" << endl; }virtual void func3() { cout << "Derive::func3()" << endl; }
protected:int _j;
};
​
typedef void(*VFPTR)();
​
void PrintVftable(VFPTR vftable[])
{for (size_t i = 0; vftable[i] != nullptr; ++i){printf("第 %d 个虚函数地址:0X%p, -->", i, vftable[i]);vftable[i]();}cout << endl;
}
​
int main()
{Derive d;VFPTR* p1 = (VFPTR*)*(int*)&d;PrintVftable(p1);
​// VFPTR* p2 = (VFPTR*)*(int*)((char*)&d + sizeof(Base1));// PrintVftable(p2)// 或者:Base2* p2 = &d;PrintVftable((VFPTR*)*(int*)p2);return 0;
}

  1. 派生类中的虚函数 func3 放在第一个继承自基类部分的虚函数表中

  2. 假设有以下场景

    Derive d;
    Base1* p1 = &d;
    p1->func1();
    Base2* p2 = &d;
    p2->func1();

    首先要确定的是

    所以在语句 p2->func1(); 中,需要修正 this 指针

    这也是为什么在 d 的两个虚表中,重写的虚函数 func1 的地址不一样


三、纯虚函数和抽象类

纯虚函数是一种特殊的虚函数,在某些情况下,在基类中不能对虚函数给出有意义的实现,就可以把它声明为纯虚函数。纯虚函数只有函数名、参数和返回值类型,没有函数体,具体实现留给派生类去做。具体语法:virtual 返回值类型 函数名(参数列表) = 0;

含有纯虚函数的类被称为抽象类(或接口类),不能实例化对象,但可以创建指针和引用

派生类必须重写抽象类中的纯虚函数,否则也属于抽象类

#include <iostream>
using namespace std;
​
class Car
{
public:virtual void Drive() = 0;
};
​
class AITO : public Car
{
public:virtual void Drive() { cout << "Intelligent" << endl; }
};
​
class AVATR : public Car
{
public:virtual void Drive() { cout << "Comfortable" << endl; }
};
​
void func1(Car* p) { p->Drive(); }
​
void func2(Car& c) { c.Drive(); }
​
int main()
{AITO aito;AVATR avatr;
​func1(&aito);  // Intelligentfunc1(&avatr);  // Comfortable
​func2(aito);  // Intelligentfunc2(avatr);  // Comfortablereturn 0;
}

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

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

相关文章

【9月比赛合集】9场可报名的「创新应用」、「数据分析」和「程序设计」大奖赛,任君挑选!

CompHub[1] 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号会推送最新的比赛消息&#xff0c;欢迎关注&#xff01; 以下信息仅供参考&#xff0c;以比赛官网为准 目录 创新应用赛&#xff08;2场比赛&#xff09;程序设计赛&#…

会话跟踪技术学习笔记(Cookie+Session)+ HTTP学习笔记

一、核心知识点&#xff08;重点&#xff09;&#xff1a; 1.1 Cookie 1. Cookie&#xff1a;是一种客户端会话技术&#xff0c;数据会被保存在客户端&#xff0c;Cookie会携带数据访问服务器&#xff0c;用以完成一次会话内多次请求间的数据共享 2. 过程&#xff1a;浏览器…

Android 1.2 开发环境搭建

目录 1.2 开发环境搭建 1.JDK安装与配置 2.开发工具二选一 3.相关术语的解析 4.ADB命令行的一些指令 5.APP程序打包与安装的流程&#xff1a; 6.APP的安装过程&#xff1a; 7.本节小结 1.2 开发环境搭建 现在主流的Android开发环境有: ①Eclipse ADT SDK ②Android Stu…

数据库概念

定义&#xff1a; 数据库&#xff08;Database 简称DB&#xff09;是持久存储有组织/可共享数据/的容器 数据库管理系统(MySQL、Oracle、DB2)是操作/和管理数据库/的软件 分类&#xff1a; 关系(型)数据库 (MySQL、Oracle、SQL Server、SQLite、DB2) 非关系(型)数据库 (Redis…

长胜证券:华为“黑科技”点燃A股炒作激情

8月29日&#xff0c;在未举行相关发布会的情况下&#xff0c;华为新款手机Mate60Pro悄然上线开售&#xff0c;并在一小时内售罄。 金融出资报记者注意到&#xff0c;跟着商场对新机重视的继续发酵&#xff0c;其中的各种技能打破也愈加受到重视&#xff0c;其影响很快扩散到资…

C++ 学习之 构造函数 和 析构函数

前言 总的来说&#xff0c;构造函数负责对象的初始化&#xff0c;而析构函数负责对象的清理和资源释放。它们是C面向对象编程中非常重要的概念&#xff0c;用于管理对象的生命周期&#xff0c;确保对象在创建和销毁时都能够正确地进行初始化和清理。 正文 看代码 class perso…

python实现pdf双页文档转png图片,png图片裁剪为左右两等分,再合并为新的pdf单页文档

一、问题引入 现有pdf双页文档如下&#xff1a; 现按照以下页码次序对pdf双页文档进行裁剪和拼接&#xff0c;其中有两点需要特别注意&#xff0c;一是封面页只裁剪中间部分&#xff0c;二是文档是从右往左的顺序排版的 二、python程序 import os import office from PIL …

STM32G030F6 (SOP-20)Cortex ® -M0+, 32KB Flash, 8KB RAM, 17 GPIOs

淘宝淘了一批 STM32G030F6P6 SOP20&#xff0e;先备注一下, 还没想到能干嘛用&#xff0e; 手上的 STM32F103C6T6还剩一些&#xff0e; 一堆 “淘宝原厂STM32F103C8T6”, 还烫着手. 理解信息: ( 逐步补充 ) System Clock GPIOs GPIOs 17 PA[7:0] : 8bits USART Timer ADC I2…

Configuring HSRP(Hot Standby Routing Protocol)

文章目录 热备份路由协议&#xff08;HSRP&#xff09;HSRP(Hot Standby Routing Protocol)工作原理在思科路由器上配置HSRP1. 路由器配置2. 分配IP地址3. 配置HSRP4. 关键指令注释5. 验证配置是否生效6.HSRP抢占配置7.HSRP Group安全配置8.HSRP切换时间更改9.HSRP版本配置10.H…

App自动化测试持续集成效率提高50%

持续集成是一种开发实践&#xff0c;它倡导团队成员需要频繁的集成他们的工作&#xff0c;每次集成都通过自动化构建&#xff08;包括编译、构建、自动化测试&#xff09;来验证&#xff0c;从而尽快地发现集成中的错误。让正在开发的软件始终处于可工作状态&#xff0c;让产品…

SpringBoot 博客网站

SpringBoot 博客网站 系统功能 登录注册 博客列表展示 搜索 分类 个人中心 文章分类管理 我的文章管理 发布文章 开发环境和技术 开发语言&#xff1a;Java 使用框架: SpringBoot jpa H2 Spring Boot是一个用于构建Java应用程序的开源框架&#xff0c;它是Spring框架的一…

【sgTransfer】自定义组件:带有翻页、页码、分页器的穿梭框组件,支持大批量数据的穿梭显示。

特性&#xff1a; 表格宽度可以自定义翻页器显示控件可以自定义列配置项可以设置显示字段列名称、宽度、字段名可以配置搜索框提示文本&#xff0c;支持搜索过滤穿梭框顶部标题可以自定义左右箭头按钮文本可以设置 sgTransfer源码 <template><div :class"$opti…

【树形权限】树形列表权限互斥选择、el-tree设置禁用等等

文章目录 一、实现如上树形列表1.1 首先要就是渲染树形列表1.2 然后通过插槽处理头部标题1.3 再通过插槽处理表格body体内容1.4 让body体中的选框和表头中的选框产生关联 二、将 el-tree 整棵树设为禁用状态三、动态表格合并 需求&#xff1a;按照权限管理配置的数据权限树展开…

Vert.x 源码解析(4.x)(一)——Future源码解析

目录 1. 简介 在现代的软件开发中&#xff0c;异步编程已经变得非常重要。它可以提高应用程序的并发性能&#xff0c;使应用程序能够更有效地处理大量的并行操作。Vert.x 是一个面向事件驱动、非阻塞的异步编程框架&#xff0c;它提供了丰富的工具来简化异步编程的复杂性。 如…

解决windows下git操作提示用户名密码错误的问题

当代码从一个平台切换到另一个平台的时候&#xff0c;需要做两步操作&#xff0c;第一步就是更新git的仓库地址&#xff0c;在项目的.git/config文件里面修改&#xff0c;这一步做完之后&#xff0c;就可以推送代码到新的仓库了&#xff0c;这里就是重点来了。 一般第一次推动代…

JVM虚拟机对象探秘

对象的创建 Java是一门面向对象的编程语言&#xff0c;创建对象通常只是通过new关键字。 对象创建过程 当Java虚拟机遇到一条字节码new指令时&#xff0c;首先将去检查这个指令的参数是否能在常量池中定位到 一个类的符号引用&#xff0c;并且检查这个符号引用&#xff08;类…

如何设计微服务

一、序幕 最近在思考&#xff0c;自己哪些不足&#xff0c;需要学习点什么&#xff1f;看着Java基础知识&#xff0c;千遍一律&#xff0c;没有太大的动力需深挖&#xff0c;只能在写业务项目的时候边写边思考边夯实自己的基础。于是看了网上的一些资料&#xff0c;结合以前面试…

linux并发服务器 —— 多线程并发(六)

线程概述 同一个程序中的所有线程均会独立执行相同程序&#xff0c;且共享同一份全局内存区域&#xff1b; 进程是CPU分配资源的最小单位&#xff0c;线程是操作系统调度执行的最小单位&#xff1b; Linux环境下&#xff0c;线程的本质就是进程&#xff1b; ps -Lf pid&…

101序列检测器

本次所做设计&#xff0c;使用数字电路芯片实现的101序列检测器。电路图如下&#xff1a; 主要首先要根据需求画出状态转移方程&#xff0c;然后写出它的逻辑表达式。最后根据所选触发器种类确定电路图。序列由按键控制输入&#xff0c;按键按下&#xff0c;代表输入1 &#xf…

RT-Thread UART

UART 简介 UART&#xff08;Universal Asynchronous Receiver/Transmitter&#xff09;通用异步收发传输器&#xff0c;UART 作为异步串口通信协议的一种&#xff0c;工作原理是将传输数据的每个字符一位接一位地传输。是在应用程序开发过程中使用频率最高的数据总线。 UART …