C++多态【下】

在这里插入图片描述

文章目录

  • 1.多态实现的底层
    • 1.1初识多态原理
    • 1.2深入理解虚函数表
      • 1.单继承虚函数表
      • 2.探究虚函数表存储数据
      • 3.知识点金
      • 4.多继承虚函数表
  • 2.题目讲解

1.多态实现的底层

1.1初识多态原理

class Dad 
{
public:virtual void Cook() { cout << "佛跳墙" << endl; }virtual void Work() { cout << "Work" << endl; }int _a = 0;
};class Son : public Dad 
{
public:virtual void Cook(){ cout << "方便面" << endl; }int _b = 0;
};void Test(Dad& p)
{p.Cook();
}int main()
{Dad dad;Test(dad);Son son;Test(son);return 0;
}

在这里插入图片描述

1.2深入理解虚函数表

1.单继承虚函数表

同类型对象共用一个虚表

若子类不重写 父类虚表指向父类的虚函数 子类虚表也指向父类的虚函数
但是vs下 不管是否重写 子类跟父类虚表都不是同一个
这样实现的理由:即便子类没有重写 但是子类有自己的虚函数时 单独创建一个虚表和父类分隔开 更有条理
子类虚函数表存储:重写的父类虚函数 没有重写的父类虚函数 自己的虚函数

2.探究虚函数表存储数据

class Dad
{
public:virtual void BuyCar(){cout << "Dad::买车-宾利" << endl;}virtual void Func1(){cout << "Dad::Func1()" << endl;}
};class Son : public Dad 
{
public:virtual void BuyCar(){cout << "Son::买车-奔驰" << endl;}virtual void Func2(){cout << "Son::Func2()" << endl;}
};typedef void(*vftptr)();
void PrintVftable(vftptr* pt)  //void PrintVftable(vftptr pt[])
{for (size_t i = 0; *(pt + i) != nullptr; ++i){printf("vft[%d]:%p->", i, pt[i]);//1.直接访问pt[i]();//2.间接访问//vftptr pf = pt[i];//pf();}cout << endl;
}
int main()
{Dad p1;Dad p2;Son s1;Son s2;//打印子类虚表PrintVftable((vftptr*)*(int*)&s1);PrintVftable((*(vftptr**)&s1));//解释见下//打印父类虚表PrintVftable((vftptr*)*(int*)&p1);PrintVftable((*(vftptr**)&p1));//解释见下return 0;
}

在这里插入图片描述
在这里插入图片描述

3.知识点金

  1. 虚表在编译阶段生成。
  2. 类实例化的对象中的虚表指针在构造函数的初始化列表初始化。
  3. 虚表存在于代码段。
    在这里插入图片描述
    在这里插入图片描述
int x = 0;static int y = 0;int* z = new int;const char* p = "xxxxxxxxxxxxxxxxxx";printf("栈对象:%p\n", &x);printf("堆对象:%p\n", z);printf("静态区对象:%p\n", &y);printf("常量区对象:%p\n", p);printf("s对象虚表:%p\n", *((int*)&s));printf("d对象虚表:%p\n", *((int*)&d1));

4.多继承虚函数表

#define _CRT_SECURE_NO_WARNINGS 
#include <iostream>
#include <list>
#include <vector>
#include <algorithm>
#include <array>
#include <time.h>
#include <queue>
using namespace std;class Dad1 
{
public:virtual void func1() { cout << "Dad1::func1" << endl;}virtual void func2(){ cout << "Dad1::func2" << endl;}
private:int a1 = 1;
};class Dad2 
{
public:virtual void func1(){ cout << "Dad2::func1" << endl; }virtual void func2(){cout << "Dad2::func2" << endl; }
private:int a2 = 2;
};class Son : public Dad1, public Dad2 
{
public:virtual void func1() {cout << "Son::func1" << endl;}virtual void func3(){ cout << "Son::func3" << endl;}
private:int aa = 3;
};typedef void(*vftptr)();
void PrintVftable(vftptr* pt)  //void PrintVftable(vftptr pt[])
{for (size_t i = 0; *(pt + i) != nullptr; ++i){printf("vft[%d]:%p->", i, pt[i]);//1.直接访问pt[i]();//2.间接访问//vftptr pf = pt[i];//pf();}cout << endl;
}int main()
{Dad1 d1;Dad2 d2;Son s;cout << "d1所占字节数为" << sizeof(d1) << endl;//8cout << "d2所占字节数为" << sizeof(d2) << endl;//8cout << "s所占字节数为"  << sizeof(s)  << endl;//20//显示虚表ⅠPrintVftable((vftptr*)(*(int*)&s)); //int只能访问4个字节 在64位下不再适用1//PrintVftable((*(vftptr**)&s)); 高级写法//显示虚表Ⅱ法一:PrintVftable((vftptr*)(*(int*)((char*)&s+sizeof(Dad1))));//PrintVftable((*(vftptr**)((char*)&s + sizeof(Dad1))));高级写法//显示虚表tⅡ法二://Dad2* ptr = &s;//PrintVftable((vftptr*)(*(int*)(ptr)));//PrintVftable((*(vftptr**)ptr)); 高级写法cout << "单独调用Son中的func1->" ;printf("%p\n", &Son::func1); //成员函数需要加&才能取到地址 普通函数名就可作为地址//普通调用s.func1();//多态调用Dad1* ptr1 = &s;ptr1->func1();Dad2* ptr2 = &s;ptr2->func1();return 0;
}

在这里插入图片描述

问题:这三次调用的func1是不是同一个函数?答案是肯定的,从运行结果最三行可以看出。但是为什么这三次调用的地址都不一样???答案见下
在这里插入图片描述
图中可以看出 调用ptr2时执行了在这里插入图片描述为什么呢?答案见下。
在这里插入图片描述
以上汇编代码仅供参考。解读:调用ptr2时,先执行了在这里插入图片描述目的是使得此时的this指针能够指向s对象的首地址。为什么ptr1调用时没有此动作?因为ptr1调用时,this指针指向Dad1部分,恰好就是s对象的首地址。而ptr2调用时,是s中间的某个位置。需要修正this指针到s对象的首地址。
所以有在这里插入图片描述。也就解释了为什么从监视窗口看到两个func1函数地址不同。实际上是同一个函数,只不过其中一个要到另一个地方,做一些特定的事情。

2.题目讲解

  1. 什么是多态?
  2. 什么是重载、重写(覆盖)、重定义(隐藏)?
  3. 多态的实现原理?
  4. inline函数可以是虚函数吗?
  5. 静态成员可以是虚函数吗?
  6. 构造函数可以是虚函数吗?
  7. 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?
  8. 对象访问普通函数快还是虚函数更快?
  9. 虚函数表是在什么阶段生成的,存在哪的?
  10. C++菱形继承的问题?虚继承的原理?
  11. 什么是抽象类?抽象类的作用?

答案:

  1. 多态是指同一种行为(方法)在不同对象上产生不同的结果。在面向对象编程中,多态是通过继承和重写(覆盖)实现的。子类可以重写父类的方法,从而产生不同的行为。

  2. 重载(Overload)是指在同一个作用域内,使用相同的函数名,但参数类型或个数不同的多个函数。

    重写(Override/覆盖)是指在派生类中重新定义(覆盖)基类中定义的虚函数,使其能够根据具体的派生类对象来执行对应的操作。

    重定义(Hide)是指在派生类中定义与基类中相同函数名的非虚函数,该函数会屏蔽基类中的同名函数,无法通过基类指针或引用调用派生类中重新定义的函数。

  3. 多态的实现原理是通过基类的指针或引用来访问派生类的对象,在运行时确定具体调用哪个类的函数。这是因为基类中的虚函数使用了虚函数表的机制,每个对象都有一个指向对应虚函数表的指针。当通过基类指针或引用调用虚函数时,根据对象的实际类型,在虚函数表中查找需要调用的函数,并执行相应的操作。

  4. inline函数可以是虚函数,但编译器会忽略inline属性,将该函数从inline函数列表中移除,因为虚函数需要放在虚函数表中。

  5. 静态成员函数不能是虚函数,因为静态成员函数没有this指针,使用 类型::成员函数 的调用方式无法访问虚函数表,所以静态成员函数无法放入虚函数表。

  6. 构造函数不能是虚函数,因为构造函数中的虚函数表指针是在构造函数初始化列表阶段才初始化的,此时对象尚未完全建立。

  7. 析构函数可以是虚函数,并且最好将基类的析构函数定义为虚函数。这样当通过基类指针或引用来删除一个派生类对象时,会调用正确的析构函数并避免内存泄漏。虚析构函数通常用于处理多态对象的释放问题。

  8. 对象访问普通函数和访问虚函数的速度相同,对于普通对象,直接调用函数就可以了,不需要查找虚函数表。而对于指针对象或引用对象,由于可能存在多态性,需要根据实际类型查找虚函数表,稍微慢一些。

  9. 虚函数表是在编译阶段生成的,一般情况下存储在代码段(常量区)。每个类有一个独立的虚函数表,其中存储了该类及其基类的虚函数信息。对象在创建时会分配一块内存用来存储动态分派所需的虚函数表指针,通过这个指针来访问虚函数表并执行对应的函数。

  10. C++中的菱形继承问题是指一个派生类同时继承了两个共同基类,而这两个基类又继承了同一个虚基类,造成了二义性和资源浪费的问题。为了解决这个问题,可以使用虚继承(virtual inheritance)来共享同一个虚基类,避免重复继承。

  11. 抽象类是指含有纯虚函数(只有函数声明,没有函数体)的类,无法实例化对象。抽象类一般用作基类,强制派生类重写纯虚函数,从而达到接口继承的目的。抽象类的作用是定义一组接口(纯虚函数),规范具体派生类的行为。

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

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

相关文章

Can‘t connect to local MySQL server through socket ‘/tmp/mysql.sock‘

最近在用django框架开发后端时&#xff0c;在运行 $python manage.py makemigrations 命令时&#xff0c;报了以上错误&#xff0c;错误显示连接mysql数据库失败&#xff0c;查看了mysql数据库初始化配置文件my.cnf&#xff0c;我的mysql.sock文件存放路径配置在了/usr/local…

【微服务部署】08-监控与告警

文章目录 1. PrometheusOperator1.1 优势1.2 配置脚本1.3 部署脚本 2. Granfana实现监控看板2.1 Granfana核心特性2.2 部署文件 3. prometheus-net收集自定义指标3.1 组件包3.2 使用场景 目前Kubernetes中最流行的监控解决方案是使用Prometheus和AlertManager 1. PrometheusOpe…

jar包或exe程序设置为windows服务

最近在使用java和python制作客户端时突发奇想&#xff0c;是否能够通过一种方法来讲jar包和exe程序打包成windows服务呢&#xff1f;简单了解了一下是可以的。 首先要用到的是winSW&#xff0c;制作windows服务的过程非常简单&#xff0c;仅需几步制作完成&#xff0c;也不需要…

ESP32-C3的存储器类型

本文主要参考ESP-IDF编程指南&#xff0c;一点小记录。 ESP32-C3的存储器有&#xff1a; ESP-IDF 区分了指令总线&#xff08;IRAM、IROM、RTC FAST memory&#xff09;和数据总线 (DRAM、DROM)。 内部SRAM的一部分是指令RAM(IRAM)。那为什么要把指令放在RAM中&#xff0c;就是…

Zookeeper简述

数新网络-让每个人享受数据的价值 官网现已全新升级—欢迎访问&#xff01; 前 言 ZooKeeper是一个开源的、高可用的、分布式的协调服务&#xff0c;由Apache软件基金会维护。它旨在帮助管理和协调分布式系统和应用程序&#xff0c;提供了一个可靠的平台&#xff0c;用于处理…

QT第二天

1.优化登陆界面&#xff0c;当点击登录按钮后&#xff0c;在该按钮对应的槽函数中&#xff0c;判断账户和密码框内的数据是否为admin和123456&#xff0c;如果账户密码匹配成功&#xff0c;则提示登陆成功并关闭登录界面&#xff0c;如果账户密码匹配失败&#xff0c;则提示登录…

嵌入式开发-11 Linux下GDB调试工具

目录 1 GDB简介 2 GDB基本命令 3 GDB调试程序 1 GDB简介 GDB是GNU开源组织发布的一个强大的Linux下的程序调试工具。 一般来说&#xff0c;GDB主要帮助你完成下面四个方面的功能&#xff1a; 1、启动你的程序&#xff0c;可以按照你的自定义的要求随心所欲的运行程序&#…

介绍PHP

PHP是一种流行的服务器端编程语言&#xff0c;用于开发Web应用程序。它是一种开源的编程语言&#xff0c;具有易学易用的语法和强大的功能。PHP支持在服务器上运行的动态网页和Web应用程序的快速开发。 PHP可以与HTML标记语言结合使用&#xff0c;从而能够生成动态的Web页面&a…

Python Opencv实践 - Harris角点检测

参考资料&#xff1a;https://blog.csdn.net/wsp_1138886114/article/details/90415190 import cv2 as cv import numpy as np import matplotlib.pyplot as pltimg cv.imread("../SampleImages/chinease_tower.jpg", cv.IMREAD_COLOR) plt.imshow(img[:,:,::-1])#…

CSS笔记(黑马程序员pink老师前端)浮动,清除浮动

浮动可以改变标签的默认排列方式。浮动元素常与标准流的父元素搭配使用. 网页布局第一准则:多个块级元素纵向排列找标准流&#xff0c;多个块级元素横向排列找浮动。 float属性用于创建浮动框&#xff0c;将其移动到一边&#xff0c;直到左边缘或右边缘触及包含块或另一个浮动框…

xss前十二关靶场练习

目录 一、xss原理和分类 1.原理 2.分类&#xff1a;xss分为存储型和反射型以及dom型 &#xff08;1&#xff09;反射性 &#xff08;2&#xff09;存储型 &#xff08;3&#xff09;dom型 二、靶场关卡练习​编辑 1.第一关 2.第二关 3.第三关 4.第四关 5.第五关 6…

Redis——认识Redis

简单介绍 Redis诞生于2009年&#xff0c;全称是Remote Dictionary Server&#xff0c;远程词典服务器&#xff0c;是一个基于内存的键值型NoSQL数据库。 特征 键值&#xff08;Key-value&#xff09;型&#xff0c;value支持多种不同数据结构&#xff0c;功能丰富单线程&…

对象模型和this指针(个人学习笔记黑马学习)

1、成员变量和成员函数 #include <iostream> using namespace std; #include <string>//成员变量和成员函数分开存储class Person {int m_A;//非静态成员变量 属于类的对象上的static int m_B;//静态成员变量 不属于类的对象上void func() {} //非静态成员函数 不…

博客程序系统其它功能扩充

一、注册功能 1、约定前后端接口 2、后端代码编写 WebServlet("/register") public class RegisterServlet extends HttpServlet {Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//设置…

《向量数据库指南》——提高向量数据库Milvus Cloud 2.3的运行效率

简介:向量数据库彻底改变了我们处理复杂数据结构的方式: 向量数据库彻底改变了我们处理复杂数据结构的方式,为高维矢量提供了高效的存储和检索。作为向量数据库专家和《向量数据库指南》的作者,我很高兴能与大家分享向量数据库运行效率方面的最新进展。在本文中,我们将探讨…

ARM编程模型-常用指令集

一、ARM指令集 ARM是RISC架构&#xff0c;所有的指令长度都是32位&#xff0c;并且大多数指令都在一个单周期内执行。主要特点&#xff1a;指令是条件执行的&#xff0c;内存访问使用Load/store架构。 二、Thumb 指令集 Thumb是一个16位的指令集&#xff0c;是ARM指令集的功能…

PandaGPT部署演示

PandaGPT 是一种通用的指令跟踪模型&#xff0c;可以看到和听到。实验表明&#xff0c;PandaGPT 可以执行复杂的任务&#xff0c;例如生成详细的图像描述、编写受视频启发的故事以及回答有关音频的问题。更有趣的是&#xff0c;PandaGPT 可以同时接受多模态输入并自然地组合它们…

嵌入式linux(imx6ull)下RS485接口配置

接口原理图如下&#xff1a; 由原理图可知收发需要收UART_CTS引脚控制,高电平时接收&#xff0c;低电平时发送。通过查看Documentation/devicetree/bindings/serial/fsl-imx-uart.yaml和Documentation/devicetree/bindings/serial/rs485.yaml两个说明文档&#xff0c;修改设备树…

Nginx__高级进阶篇之LNMP动态网站环境部署

动态网站和LNMP&#xff08;LinuxNginxMySQLPHP&#xff09;都是用于建立和运行 web 应用程序的技术。 动态网站是通过服务器端脚本语言&#xff08;如 PHP、Python、Ruby等&#xff09;动态生成网页内容的网站。通过这种方式&#xff0c;动态网站可以根据用户的不同请求生成不…

分类算法系列⑤:决策树

目录 1、认识决策树 2、决策树的概念 3、决策树分类原理 基本原理 数学公式 4、信息熵的作用 5、决策树的划分依据之一&#xff1a;信息增益 5.1、定义与公式 5.2、⭐手动计算案例 5.3、log值逼近 6、决策树的三种算法实现 7、API 8、⭐两个代码案例 8.1、决策树…