【C++】多态的原理

目录

一、虚函数表 

1、虚函数表的定义

 2、虚函数表特性

3、虚表的打印

二、多态的原理

三、多态的相关问题

1、指针偏移问题

2、输出的程序是什么?

3、输出的程序是什么?


【前言】

上一篇我们学习了多态的基础知识,这一篇我将带着大家深入多态学习,了解多态的原理。【多态的基本介绍】

一、虚函数表 

1、虚函数表的定义

// 这里常考一道笔试题:sizeof(Base)是多少?
class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}private:int _b = 1;char _ch;
};int main()
{cout << sizeof(Base) << endl;Base bb;return 0;
}

很多人会因为内存对齐认为答案是8,其实不然,答案是12.

这是因为类里面,除了_bb成员,还多一个__vfptr 放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表,存的是虚函数的地址。指针(v 代表 virtual,f 代表 function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表,本质是一个虚函数指针数组,一般情况这个数组最后面放了一个nullptr

 2、虚函数表特性

派生类的虚表是如何形成的呢?

派生类对象中有一个虚表指针,是由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的,另一部分是自己的成员。

a.先将基类中的虚表内容拷贝一份到派生类虚表中

b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数

c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。

d.要理解将子类赋值给父类对象时,切片过程中,子类的虚表并没有拷贝切过去。这个过程是不会拷贝子类的虚表的。因为如果拷贝子类的虚表赋值给父类了,那么当指向的对象是这个父类时,到这个父类的虚表里找,找的那就是子类的虚函数了,而不是父类的虚函数

多态是如何利用虚函数表实现指向父类调用父类函数,指向子类调用子类函数的呢?

当指向父类对象时,就会去父类对象的虚表里找虚函数,当指向子类对象时,就会去子类对象的虚表里找虚函数。
中间发生了切割,本质上都是指向了父类数据,看到的还是父类对象。因为派生类继承不仅继承父类的所有数据,也将父类的虚表继承下来了派生类会将重写的虚函数地址覆盖原来的基类的虚函数。这样就可以实现指向父类调用父类函数,指向子类调用子类函数。

同种类型的函数会被放在同一个虚函数表,同类型的对象会指向同一个虚表

Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函 数,所以不会放进虚表。 

我们知道要达到多态,有两个条件,一个是虚函数覆盖,一个是对象的指针或引用调用虚函数。反思一下为什么一定要满足这个条件呢?为什么函数直接调用不行呢?

当使用指针或者引用时,父类的对象会指向父类的虚表,子类会继承父类的接口并进行重写指向子类的虚表。这样就可以实现指向父类调用父类函数,指向子类调用子类函数。

对于对象来说,指向父类调用父类函数,指向子类时会面临一个问题,父类的虚表会不会被子类拷贝,如果不拷贝,父类成员的虚表里面永远只有父类的虚函数,这显然是不行的。如果拷贝,虚表指向不明确,是原本父类的虚表还是子类拷贝过来的虚表。所以对象的切片只拷贝成员,不拷贝虚表。

虚函数存在哪的?虚表存在哪的?

虚函数表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针

虚表是什么阶段生成的?对象中虚表指针什么时候初始化?

虚表是在编译过程中生成的,因为编译过程中会生成地址。虚表指针是在构造函数中通过初始化列表中初始化。 

理解虚函数为什么要重写?

只有虚函数重写,派生类的虚表里才可以存真正派生类虚函数,因为这个虚表是从父类继承下来的,里面都是父类的虚函数地址。而只有派生类虚函数重写后,才可以将重写的虚函数地址覆盖上去。这样就可以做到指向父类调用父类虚表中对应的虚函数,指向子类,调用子类虚表中对应的虚函数。

3、虚表的打印

 有时候监视器窗口虚表不一定全部显示出来,所以我们可以写一个打印虚表的代码,便于我们自己观察。

虚表是一个函数指针数组,但是函数指针的类型比较复杂,所以我们重定义一下函数指针,增强代码可读性,需要注意:typedef void(*)() VF_PTR 函数指针定义名字需要放在中间

 不同对象虚表里面对象不一样多,vs编译器在虚表最后都会放置一个空,所以我们可以利用这个原理实现for循环。

二、多态的原理

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 mike;Func(mike);Student johnson;Func(johnson);return 0;
}

当p是指向mike对象时,p->BuyTicket在mike的虚表中找到虚函数是Person::BuyTicket。当p是指向johnson对象时,p->BuyTicket在johson的虚表中找到虚函数是Student::BuyTicket。 这样就实现出了不同对象去完成同一行为时,展现出不同的形态。

我们需要知道满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象的中取找的。不满足多态的函数调用时编译时确认好的。

三、多态的相关问题

1、指针偏移问题

class Base1 {  public:  int _b1; };class Base2 {  public:  int _b2; };class Derive : public Base1, public Base2 { public: int _d; };int main(){Derive d;Base1* p1 = &d;Base2* p2 = &d;Derive* p3 = &d;return 0;}A:p1 == p2 == p3   B:p1 < p2 < p3    C:p1 == p3 != p2   D:p1 != p2 != p3

【答案】选C 

2、输出的程序是什么?

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;}A: A->0    B: B->1   C: A->1   D: B->0   E: 编译出错    F: 以上都不正确

 【答案】选B

首先判断是否形成多态:

1、虚函数的重写--三同(函数名、参数、返回值)2、父类指针或者引用调用。

是多态。p调用 test函数,test 调用 func函数,func 函数参数的类型是A* this,this 调用class B的 func,派生类继承父类的成员函数,也会继承成员函数的缺省参数,所以val=1.

3、输出的程序是什么?

class A
{
public:virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }
};
class B : public A
{
public:void func(int val = 0) { std::cout << "B->" << val << std::endl; }virtual void test() { func(); }
};
int main(int argc, char* argv[])
{B* p = new B;p->test();return 0;
}A: A->0    B: B->1   C: A->1   D: B->0   E: 编译出错    F: 以上都不正确

【答案】选D

首先判断是否形成多态:

1、虚函数的重写--三同(函数名、参数、返回值)2、父类指针或者引用调用。

不是多态,test 直接在class B调用func函数。

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

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

相关文章

HarmonyOS 应用开发之通过数据管理服务实现数据共享静默访问

场景介绍 典型跨应用访问数据的用户场景下&#xff0c;数据提供方会存在多次被拉起的情况。 为了降低数据提供方拉起次数&#xff0c;提高访问速度&#xff0c;OpenHarmony提供了一种不拉起数据提供方直接访问数据库的方式&#xff0c;即静默数据访问。 静默数据访问通过数据…

leetcode1379--找出克隆二叉树中的相同节点

1. 题意 对于一个克隆的二叉树&#xff0c;找到与原二叉树相同的节点。 找出克隆二叉树中的相同节点 2. 题解 直接dfs搜索即可 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode(int…

Incus:新一代容器与虚拟机编排管理引擎

Incus是什么&#xff1f; Incus是一个用于编排管理应用型容器、系统型容器及虚拟机实例的管理工具。它是对 Canonical LXD 的继承与发展&#xff0c;引入了更多的存储驱动支持。 Incus项目的产品地址&#xff1a;Linux Containers - Incus - Introduction 在 LXC-Incus 项目…

Springboot3 集成knife4j(swagger)

knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui,取名kni4j是希望它能像一把匕首一样小巧,轻量,并且功能强悍! 官网地址&#xff1a; Knife4j 集Swagger2及OpenAPI3为一体的增强解决方案. | Knife4j 本文以Springboot3版本集成kn…

Qt文本搜索

效果&#xff1a; 按下ctrlf跳出搜索框&#xff0c;然后支持搜索next或者previous。支持搜索下一步和上一步。 嵌入框的实现 #ifndef POPFINDBOX_H #define POPFINDBOX_H#include <QWidget>class QLineEdit; class QPushButton; class PopFindBox : public QWidget {Q_O…

深入理解Spring Boot Controller层的作用与搭建过程

在现代的Web应用开发中&#xff0c;Spring Boot作为一款快速、便捷的Java框架&#xff0c;为开发者提供了丰富的功能和便利的工具。其中&#xff0c;Controller层作为Spring Boot应用的核心之一&#xff0c;承担着处理HTTP请求、调用业务逻辑、数据封装和返回等重要任务。本文将…

KnowLog:基于知识增强的日志预训练语言模型|顶会ICSE 2024论文

徐波 东华大学副教授 东华大学计算机学院信息技术系副系主任&#xff0c;复旦大学知识工场实验室副主任&#xff0c;智能运维方向负责人。入选“上海市青年科技英才扬帆计划”。研究成果发表在IJCAI、ICDE、ICSE、ISSRE、ICWS、CIKM、COLING等国际会议上&#xff0c;曾获中国数…

Tauri 进阶使用与实践指南

Tauri 进阶使用与实践指南 调试技术 在 Tauri 应用开发中&#xff0c;调试分为两大部分&#xff1a;Web 端与 Rust 控制台。 Web 端调试 在 Web 端界面&#xff0c;可以直接采用浏览器内置的开发者工具进行调试。在 Windows 上&#xff0c;可以通过快捷键 Ctrl Shift i 打…

【turtle海龟先生】神奇的“圆”,画,太极圈,铜钱古币

turtle画圆三步法 步骤: 1、导入turtle库 2、确定半径&#xff0c;画圆(circle ) 3、结束(done ) turtle 库中提供一个直接画圆的函数 turtle.circle&#xff08;半径&#xff09;#半径单位为像素 例&#xff1a; turtle.circle ( 100 ) 表示绘制一个半径为100像素长度的圆形 …

基于ROS的地图发布和加载(GAZEBO仿真)

文章目录 环境配置启动仿真运动控制地图保存地图加载Q&A环境配置 cd ~/catkin_ws/src git clone https://github.com/wh200720041/warehouse_simulation_toolkit.git cd .. catkin_make source ~/catkin_ws/devel/setup.bash启动仿真 roslaunch warehou

js项目中常用的方法汇总

做了很多项目&#xff0c;发现有很多公用的js方法可以总结出来&#xff0c;不同的项目却可以使用这些通用的方法去解决业务上面的问题; 时间常用方法和冷门方法 请参考timeUtil 获取去当前网址url export function GetCurrentProjectUrl(pathname: string) {const location …

PostgreSQL备份还原数据库

1.切换PostgreSQL bin目录 配置Postgresql环境变量后可以不用切换 pg_dump 、psql都在postgresql bin目录下&#xff0c;所以需要切换到bin目录执行命令 2.备份数据库 方式一 语法 pg_dump -h <ip> -U <pg_username> -p <port> -d <databaseName>…

一、c++代码中的安全风险-strlen

一、函数 strlen 在C语言中,strlen是一个函数,计算字符串长度,遇见 \0 停止。如果没有 \0 将在内存中一直寻找,直到寻找到了\0停止。所以strlen还是存在很大的风险的。而且参数必须是字符型指针(char*)。当数组名作为参数传入时,实际上数组就退化成指针了…

2023年第三届 “联想杯”全国高校程序设计在线邀请赛暨第五届上海理工大学程序设计竞赛(同步赛)

A-3的倍数 首先求出sum&#xff0c;如果sum为3的倍数&#xff0c;那么直接可以降序 如果sum%31&#xff0c;那么优先删除一个对3取模余1的数&#xff0c;如果没有则删除两个对3取模余2的数 如果sum%32&#xff0c;那么优先删除一个对3取模余2的数&#xff0c;如果没有则删除…

【软件工程】详细设计(二)

这里是详细设计文档的第二部分。前一部分点这里 4. 学生端模块详细设计 学生端模块主要由几个组件构成&#xff1a;学生登录界面&#xff0c;成绩查询界面等界面。因为学生端的功能相对来说比较单一&#xff0c;因此这里只给出两个最重要的功能。 图4.1 学生端模块流程图 4.…

软考高级架构师:性能评价方法概念和例题

一、AI 讲解 性能评价是衡量计算机系统或其组件在指定条件下执行预期任务的有效性的一种方式。性能评价的方法主要可以分为几种&#xff0c;每种方法都有其特点和适用场景。 性能评价方法 方法描述时钟频率法通过计算机的时钟频率来评估性能&#xff0c;时钟频率越高&#x…

大话设计模式之状态模式

状态模式是一种行为设计模式&#xff0c;它允许对象在其内部状态发生变化时改变其行为。在状态模式中&#xff0c;对象将其行为委托给当前状态对象&#xff0c;从而在不同的状态下执行不同的行为&#xff0c;而不必在对象自身的代码中包含大量的条件语句。 通常&#xff0c;状…

mysql 相关链接与子查询的练习,以及索引视图的简单概述

4月1日 索引与视图 一 销售查询问题复习链接和子查询 1&#xff09;子查询相关sql语句 -- 结果返回一个值 select 查询字段 from 表 where 字段 [ > < <>] (子查询的内容)-- 单列多行 select 查询字段 from 表 where 字段 in (子查询)-- 多列多行 select 查询字…

06 监听器

文章目录 SessionAttListenerDemo.javaSessionListenerDemo.javaProductController.java SessionAttListenerDemo.java package com.aistart.listener;import javax.servlet.ServletContext; import javax.servlet.annotation.WebListener; import javax.servlet.http.HttpSess…

Tensorboard使用教程

Pytorch(九) —— Tensorboard(当有了tensorboard日志文件怎么可视化它)(同时显示多个模型)(vscode的tensorboard)(TensorboardX)_tensorboard --logdir-CSDN博客文章浏览阅读9.7k次&#xff0c;点赞10次&#xff0c;收藏56次。tensorboard.pyfrom tensorboardX import Summary…