[C++] 多态(下) -- 多态原理 -- 动静态绑定

在这里插入图片描述

文章目录

  • 1、多态原理
  • 2、动态绑定和静态绑定
  • 3、单继承和多继承关系的虚函数表
    • 3.1 单继承中的虚函数表
    • 5.2 多继承中的虚函数表

上一篇文章我们了解了虚函数表,虚函数表指针,本篇文章我们来了解多态的底层原理,更好的理解多态的机制。
[C++] 多态(上) – 抽象类、虚函数、虚函数表

1、多态原理

下面这段代码中,Func函数传Person调用的Person::BuyTicket,传Student调用的是Student::BuyTicket,这就是多态调用,但是这里我们并不知道原理是什么,接下来我们就来了解一下原理。

class Person 
{
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person 
{
public:virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
void Func(Person* p)
{p->BuyTicket();
}
int main()
{Person p;Func(&p);Student s;Func(&s);return 0;
}

在这里插入图片描述

  1. 观察监视窗口我们看到,p是指向p对象时,p->BuyTicket在p的虚表中找到虚函数是Person::BuyTicket。
  2. 观察监视窗口我们看到,p是指向s对象时,p->BuyTicket在s的虚表中找到虚函数是Student::BuyTicket。从Student中切片出来的父类,call BuyTicket的地址已经发生了改变。
  3. 这样就实现出了不同对象去完成同一行为时,展现出不同的形态。
  4. 反过来思考我们要达到多态,有两个条件:1、一个是虚函数覆盖;2、一个是对象的指针或引用调用虚函数。
  5. 再通过下面的汇编代码分析,看出满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象的中取找的。不满足多态的函数调用时编译时确认好的。
    多态调用:运行时,去虚表中找到地址去调用函数
    普通调用:编译时,确定函数地址
    在这里插入图片描述

2、动态绑定和静态绑定

  1. 静态绑定又称为前期绑定(早绑定),在 程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载;
  2. 动态绑定又称后期绑定(晚绑定),是在 程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态;
  3. 上面的买票汇编就解释了什么是具体的动态绑定和静态绑定。

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

3.1 单继承中的虚函数表

class Base
{
public:virtual void func1(){cout << "Base::func1" << endl;}virtual void func2(){cout << "Base::func2" << endl;}
private:int _a;
};
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; }
private:int b;
};
int main()
{Base b;Derive d;return 0;
}

在这里插入图片描述

观察上图中的监视窗口中我们发现看不见func3和func4。这里是编译器的监视窗口故意隐藏了这两个函数,也可以认为是他的一个小bug。那么我们如何查看d的虚表呢?我们写一段代码来将需表中的虚函数打印出来。

代码思路: 取出b、d对象的头4bytes,就是虚表的指针,前面我们说了虚函数表本质是一个存虚函数指针的指针数组,这个数组最后面放了一个nullptr。
1.先取b的地址,强转成一个int的指针;
2.再解引用取值,就取到了b对象头4bytes的值,这个值就是指向虚表的指针;
3.再强转成VFPTR
,因为虚表就是一个存VFPTR类型(虚函数指针类型)的数组;
4.虚表指针传递给PrintVTable进行打印虚表;
5.需要说明的是这个打印虚表的代码经常会崩溃,因为编译器有时对虚表的处理不干净,虚表最后面没有放nullptr,导致越界,这是编译器的问题。我们只需要点目录栏的-生成-清理解决方案,再编译就好了。

typedef void(*VFPTR) (); // 重命名函数指针
void PrintVTable(VFPTR vTable[])
{// 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数cout << " 虚表地址->" << vTable << endl;for (int i = 0; vTable[i] != nullptr; ++i){printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);VFPTR f = vTable[i];f();}cout << endl;
}
int main()
{Base b;Derive d;VFPTR* vTableb = (VFPTR*)(*(int*)&b);PrintVTable(vTableb);VFPTR* vTabled = (VFPTR*)(*(int*)&d);PrintVTable(vTabled);return 0;
}

在这里插入图片描述

5.2 多继承中的虚函数表

class Base1 
{
public:virtual void func1() { cout << "Base1::func1" << endl; }virtual void func2() { cout << "Base1::func2" << endl; }
private:int b1;
};
class Base2 
{
public:virtual void func1() { cout << "Base2::func1" << endl; }virtual void func2() {cout << "Base2::func2" << endl; }
private:int b2;
};
class Derive : public Base1, public Base2 
{
public:virtual void func1() { cout << "Derive::func1" << endl; }virtual void func3() { cout << "Derive::func3" << endl; }
private:int d1;
};typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{cout << " 虚表地址>" << vTable << endl;for (int i = 0; vTable[i] != nullptr; ++i){printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);VFPTR f = vTable[i];f();}cout << endl;
}
int main()
{Derive d;VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);PrintVTable(vTableb1);VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));PrintVTable(vTableb2);return 0;
}

观察下图可以看出:多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中。
在这里插入图片描述

最后菱形继承、菱形虚拟继承就不再,因为正常情况下很少用,这里就不再多讲了。

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

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

相关文章

数据分析场景下,企业大模型选型的思路与建议

来源/作者&#xff1a;爱分析 随着大模型带来能力突破&#xff0c;让AI与数据分析相互结合&#xff0c;使分析结果更好支撑业务&#xff0c;促进企业内部数据价值释放&#xff0c;成为了当下企业用户尤为关注的话题。本次分享主要围绕数据分析场景下大模型底座的选型思路&#…

opencv 入门一(显示一张图片)

头文件添加如下&#xff1a; 库目录添加如下&#xff1a; 依赖的库如下&#xff1a; #include <iostream> #include "opencv2/opencv.hpp" int main(int argc,char ** argv) { cv::Mat img cv::imread(argv[1], -1); if (img.empty()) return -1; …

sourcetree 无效的源路径 细节提示:系统找不到指定的文件

工具–>选项–>git 直接下拉到底 点击红框&#xff0c;重新下载一个内嵌git就可以了 我感觉是因为改变了原有git安装路径的问题

MFC 窗口创建过程与消息处理

目录 钩子简介 代码编写 窗口创建过程分析 消息处理 钩子简介 介绍几个钩子函数&#xff0c;因为它们与窗口创建工程有关 安装钩子函数 HHOOK SetWindowsHookExA([in] int idHook,[in] HOOKPROC lpfn,[in] HINSTANCE hmod,[in] DWORD dwThreadId ); 参数说明…

深度学习笔记_7经典网络模型LSTM解决FashionMNIST分类问题

1、 调用模型库&#xff0c;定义参数&#xff0c;做数据预处理 import numpy as np import torch from torchvision.datasets import FashionMNIST import torchvision.transforms as transforms from torch.utils.data import DataLoader import torch.nn.functional as F im…

是什么导致了我孩子的听力损失?

是什么导致了我孩子的听力损失&#xff1f; 有些婴儿天生就有听力损失&#xff0c;这被称为先天性听力损失。许多不同的因素都可能导致这种类型的听力损失&#xff0c;但并不总是能够确定确切的原因。在大约一半的病例中&#xff0c;原因是遗传的&#xff0c;也就是说&#xff…

深度学习 tensorflow基础介绍

深度学习是一种基于人工神经网络的机器学习方法&#xff0c;其目标是通过模仿人脑的结构和功能&#xff0c;实现对大量复杂数据的学习和理解。它可以在图像识别、语音识别、自然语言处理等领域取得惊人的成就。 深度学习的引入引出了TensorFlow&#xff0c;它是一个由Google Br…

DBeaver Ultimate for Mac/win:掌握数据库的终极利器,助您高效管理数据!

在当今数字化时代&#xff0c;数据管理变得越来越重要。而作为一款功能强大的数据库管理工具&#xff0c;DBeaver Ultimate&#xff08;简称DBU&#xff09;助您轻松应对各种复杂的数据管理任务。无论您是数据库管理员、开发人员还是数据分析师&#xff0c;DBU都能为您提供全面…

带你学C语言~指针(2)

目录 &#x1f3c9;前言 &#x1f680; 数组名的理解 &#x1f680;使用指针访问数组 ✈一维数组传参的本质 ✈冒泡排序 &#x1f3c6;二级指针 &#x1f3c6;指针数组 &#x1f3c6;指针数组模拟二维数组 &#x1f389;结束语 &#x1f3c9;前言 上一章&#xff0c;小…

关于“Python”的核心知识点整理大全28

目录 11.1.5 添加新测试 11.2 测试类 11.2.1 各种断言方法 unittestModule中的断言方法&#xff1a; ​编辑11.2.2 一个要测试的类 survey.py language_survey.py 11.2.3 测试 AnonymousSurvey 类 test_survey.py 往期快速传送门&#x1f446;&#xff08;在文章最后&…

计算机操作系统-第十九天

目录 调度器/调度程序 闲逛进程 调度器/调度程序 ②、③由调度程序引起&#xff0c;调度程序决定了&#xff1a;让谁运行&#xff08;调度算法&#xff09;运行多长时间&#xff08;时间片大小&#xff09; 调度时机&#xff08;什么事件会触发”调度程序“&#xff09;&…

proxysql读写分离组件部署

一、前言 在mysql一主两从架构的前提下&#xff0c;引入读写分离组件&#xff0c;可以极大的提高mysql性能&#xff0c;proxysql可以在高可用mysql架构发生主从故障时&#xff0c;进行自动的主从读写节点切换&#xff0c;即当mysql其他从节点当选新的主节点时&#xff0c;proxy…

HuatuoGPT

文章目录 HuatuoGPT 模型介绍LLM4Med&#xff08;医疗大模型&#xff09;的作用ChatGPT 存在的问题HuatuoGPT的特点ChatGPT 与真实医生的区别解决方案用于SFT阶段的混合数据基于AI反馈的RL 评估单轮问答多轮问答人工评估 HuatuoGPT 模型介绍 HuatuoGPT&#xff08;华佗GPT&…

Elasticsearch 向量相似搜索

Elasticsearch 向量相似搜索的原理涉及使用密集向量(dense vector)来表示文档,并通过余弦相似性度量来计算文档之间的相似性。以下是 Elasticsearch 向量相似搜索的基本原理: 向量表示文档: 文档的文本内容经过嵌入模型(如BERT、Word2Vec等)处理,得到一个密集向量(den…

Semaphore 详解

1、Semaphore 是什么 Semaphore 通常我们叫它信号量&#xff0c; 可以用来控制同时访问特定资源的线程数量&#xff0c;通过协调各个线程&#xff0c;以保证合理的使用资源。 可以把它简单的理解成我们停车场入口立着的那个显示屏&#xff0c;每有一辆车进入停车场显示屏就会…

JDK各个版本特性讲解-JDK13特性

JDK各个版本特性讲解-JDK13特性 一、JAVA13概述二、语法层面特性1.switch表达式(预览)2.文本块(预览)2.1 概念2.2 问题2.3 目标2.4 语法细节1 基本使用2.5 语法细节2 编译器在编译时,会删除多余的空格2.6 语法细节3 转义字符2.7 语法细节4 文本块连接 三、API层次特性1.重新实现…

13、Kafka副本机制详解

Kafka 副本机制详解 1、副本定义2、副本角色3、In-sync Replicas&#xff08;ISR&#xff09;4、Unclean 领导者选举&#xff08;Unclean Leader Election&#xff09; 所谓的副本机制&#xff08;Replication&#xff09;&#xff0c;也可以称之为备份机制&#xff0c;通常是指…

为什么我的对话框创建失败了?菜鸟错误1

对话框中的资源要么被定义为一个整数&#xff0c;要么被定义为一个字符串。 仅仅一个简单的错误将会将其中的一个类型错误的变成另一个类型。我们来看一个例子。 >> 请移步至 www.topomel.com 以查看图片 << 你是否能发现其中的两处 “菜鸟级错误” ? 如果先获…

Elasticsearch:生成 AI 中的微调与 RAG

在自然语言处理 (NLP) 领域&#xff0c;出现了两种卓越的技术&#xff0c;每种技术都有其独特的功能&#xff1a;微调大型语言模型 (LLM) 和 RAG&#xff08;检索增强生成&#xff09;。 这些方法极大地影响了我们利用语言模型的方式&#xff0c;使它们更加通用和有效。 在本文…

Linux系统管理、服务器设置、安全、云数据中心

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 我们来快速了解liunx命令 文章目录 前言解析命令提示符linux的文件和目录文件和目录管理文件操作 进程管理命令系统管理网络管理 书籍推荐 本文以服务器最常用的CentOS为例 解析命令提示…