当前位置: 首页 > news >正文

C++学习之多态详解

目录

多态的实现

例题

重载 重写  重定义的区别

抽象类

多态实现原理


多态的实现

C++中的多态是指,当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。在C++中,通过将基类中的成员函数声明为虚函数,即可实现多态。

多态的发生是在继承的前提条件上,且要满足两个重要条件,否则都不能是多态:

1.虚函数的重写(要求三同,同函数名,同返回类型,同参数)-协变除外

2.父类的指针或者引用去调用函数

这与我们普通调用函数时所观察的函数类型不一样,多态调用看的是调用指针或者引用指向的对象,指向父类调用父类函数,指向子类调用子类的函数,这里他看的是指针或者引用指向的对象。

其次虚函数的重写存在两个例外:

1. 协变(基类与派生类虚函数返回值类型不同)
派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指
针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。
class A{};
class B : public A {};
class Person {
public:virtual A* f() {return new A;}
};
class Student : public Person {
public:virtual B* f() {return new B;}
};
2. 析构函数的重写(基类与派生类析构函数的名字不同)
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,
都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,
看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处
理,编译后析构函数的名称统一处理成destructor。
class Person {
public:virtual ~Person() { cout << "~Person()" << endl; }
};
class Student : public Person {
public:virtual ~Student() { cout << "~Student()" << endl; }
};
// 只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函
int main()
{Person* p1 = new Person;Person* p2 = new Student;delete p1;//调父类析构//此时子类析构调用虚析构且这里的析构函数是一种重写delete p2;//先调子类析构释放子类自己的那一部分空间,再调父类析构,释放剩下的父类的空间return 0;
}

其次我们要知道,只有对于 new子类对象给父类指针时,才会需要调用虚析构,去释放除了子类本身的那一部分空间,还要释放继承父类的那一部分空间,否则会造成内存泄漏。

例题

下面用几道题检验我们的水平:

这里正确答案选择c项,对于p1它是父类指针B1指向子类对象,由于切片的原因,所以p1就是表示Base1的空间同理p2指向Base2的空间。但是p3只想自己,也就是它包含了继承的父类的空间,按照声明的顺序,p3指向Base1+Base2+Derive,首地址的话就是Base1,故选择c。

该题正确答案是B,相信大家可能都会选择D项,首先我们知道p指针是一个子类指针,但他继承了父类的成员函数,所以调用test是父类的函数,test再次调用func函数(这里的调用还是父类this调用func函数,继承父类的),由于指向的对象是子类对象,且满足函数重写,故这里会去调用子类的func,但是记住一点,子类的函数只会重写函数体,对于参数和函数名函数类型都是继承父类的,所以这里的缺省参数应该还是父类里的。

重载 重写  重定义的区别

总的来说对于继承不是重写就是重定义,函数重载参数不同(参数类型,参数个数,参数类型顺序)。

抽象类

当一个基类的成员函数不仅仅添加了virtual,并且函数体为空,如:Drive()这个函数

class Car
{
public:
virtual void Drive() = 0;
};
class Benz :public Car
{
public:virtual void Drive(){cout << "Benz-舒适" << endl;}
};void Test()
{
Car* pBenz = new Benz;pBenz->Drive();
}

这样的函数我们将它称为纯虚函数,这样的类我们叫做抽象类。抽象类不能直接实例化对象

其次关键字overried可以检查虚函数是否完成重写。

多态实现原理

那么虚函数这种是怎么实现的呢?

在此之前我们先了解一下虚函数表:

首先对于虚函数的实现,在类中会有一个虚函数表指针,我们也可以根据类的大小看到有一个指针。

那么这个虚函数表指针是干嘛用的呢?

实际上一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。
虚函数表本质上是一个指针数组,存放虚函数的指针。
父类是这样,那对于继承它的子类呢?我们用如下代码观察:
class Person {
public:virtual void Bytiket(){ cout << "成人买票全价" << endl; }virtual void Job(){ cout << "我是医生" << endl;}void habit(){cout << "打球" << endl;}private:int _b;
};
class Student : public Person {
public:virtual void Bytiket() { cout << "学生买票半价" << endl; }
private:int _d;
};
void Func(Person &p)
{p.Bytiket();}
int main()
{Person p;Student s;Func(p);Func(s);return 0;
}

首先对于父类,监视窗口并不能看到真正的情况,我们利用内存窗口再进行观察:

&p

 再详细的观察vfptr里面的内容

从内从中我们看到了确实和我们理解的一样,就是指针数组,里面存放着函数指针。

 此时我们再观察派生类里面的内容:

&s

vfptr

 可以看到子类中也有一个虚表指针,而且这与父类的虚函数表指针不一样,可以看到两者的虚函数指针地址都不一样,但是仔细观察里面存放的各个虚函数,可以看到第一个虚函数指针与父类的不一样,而第二个虚函数指针与父类的一样。

仔细一想我们大概就知道原因了,我们知道虚函数的重写其实是虚函数的覆盖,子类将虚函数表拷贝过来,在我们重写了某一个虚函数时,对应的虚函数指针就会被覆盖成新的,当我们不重写时,对应的虚函数地址没有发生改变,因此虚函数的重写本质上就是虚函数指针的覆盖。

完成覆盖后,当我们利用父类的指针或者引用指向子类对象,在调用时,就会调用完成覆盖后的虚函数的地址(新的虚表),此时调用的就是子类中重写的方法,这也就是我们会说调用的函数和指向的对象有关,指向子类调用子类的,指向父类调用父类的。本质就是指向某个对象的虚函数表。

那么又有一个问题我们也可以仔细想象了:为什么必须是父类对象的指针或者引用,对象就不行呢?

 了解到虚函数表的存在,我们再次思考,对象的指针或者引用那就是代表父类的这一部分的空间,指向子类对象时,中间不产生临时对象,可以当作切片剩下父类的那一部分,引用也就是直接引用那一部分。所以父类的引用与指针相当于就是子类中父类的那一部分空间,而我们用的是父类对象的话,父类对象指向子类对象,单单就是把子类中那父类的一部分给给父类但是不包括虚函数表,没有虚函数表多态就无法实现,故此必须是父类的指针或引用指向子类对象。

至于这里虚函数表不能拷贝,在设计之时就已经必须这样规定,如果虚函数指针也能被拷贝,那就全乱了,在调用时,该访问哪一个虚表?

相关文章:

C++学习之多态详解

目录 多态的实现 例题 重载 重写 重定义的区别 抽象类 多态实现原理 多态的实现 C中的多态是指&#xff0c;当类之间存在层次结构&#xff0c;并且类之间是通过继承关联时&#xff0c;就会用到多态。多态意味着调用成员函数时&#xff0c;会根据调用函数的对象的类型来执…...

项目经理之识别项目干系人

项目干系人管理是项目管理中的重要一环&#xff0c;识别和管理好项目干系人是成功实施项目的关键之一。本文将介绍4321项目干系人识别方法、干系人等级册以及五步判断法等工具&#xff0c;帮助项目经理更好地识别和管理项目干系人。同时&#xff0c;本文还将介绍干系人能量方格…...

文件列表创建工具 Nifty File Lists mac中文版功能特色

Nifty File Lists mac是一款文件列表创建工具&#xff0c;全面的元数据支持&#xff0c;涵盖了从基本文件信息&#xff0c;如文件名、路径、大小、创建和修改日期等等内容。 Nifty File Lists mac功能特色 全面的 元数据支持强大的多线程元数据提取系统涵盖了从基本文件信息&a…...

人人自媒体的时候,Ai绘画还值得踏入吗?

前言 先说结论&#xff0c;如果你不打算涉足自媒体&#xff0c;平时也从不上网发什么内容去展示自己的话&#xff0c;其实AI绘画对你来说意义不大。但如果你对自媒体感兴趣&#xff0c;会涉及发作品&#xff0c;发内容&#xff0c;甚至去设计图片&#xff0c;那么AI绘画值得你…...

最近学习内容(2023-10-21)

最近学习内容 Linux编译链接命令一条有用的删除可执行文件的bash命令gcc 在macos 的编译选项&#xff0c;其中-g会生成一个.dSYM文件夹to long don’t read 工具的使用gnu bintuils 的使用&#xff0c;但是很可惜macos上的是Mach-O&#xff0c;不是ELFaxel多线程下载器和其余的…...

Java设计模式 | 基于订单批量支付场景,对策略模式和简单工厂模式进行简单实现

基于订单批量支付场景&#xff0c;对策略模式和简单工厂模式进行简单实现 文章目录 策略模式介绍实现抽象策略具体策略1.AliPayStrategy2.WeChatPayStrategy 环境 使用简单工厂来获取具体策略对象支付方式枚举策略工厂接口策略工厂实现 测试使用订单实体类对订单进行批量支付结…...

【组件专题介绍】什么是组件?

组件定义 卡耐基梅隆大学&#xff1a; 一个不透明的功能实体&#xff0c;能够被第三方组装&#xff0c;且符合一个构件模型。 计算机百科全书&#xff1a; 是软件系统中具有相对独立功能、接口由契约指定、和语境有明显依赖关系、可独立部署、可组装的软件实体。 软件构件…...

Mybatis拦截器

MyBatis插件介绍 MyBatis提供了一种插件(plugin)的功能&#xff0c;虽然叫做插件&#xff0c;但其实这是拦截器功能。 MyBatis允许使用者在映射语句执行过程中的某一些指定的节点进行拦截调用&#xff0c;通过织入拦截器&#xff0c;在不同节点修改一些执行过程中的关键属性&…...

【项目设计】网络对战五子棋(上)

想回家过年… 文章目录 一、项目前置知识1. websocketpp库1.1 http1.0/1.1和websocket协议1.2 websocketpp库接口的前置认识1.3 搭建一个http/websocket服务器 2. jsoncpp库3. mysqlclient库 二、 项目设计1. 项目模块划分2. 实用工具类模块2.1 日志宏封装2.2 mysql_util2.3 j…...

【Overload游戏引擎细节分析】鼠标键盘控制摄像机原理

在上文中分析了摄像机类的实现&#xff0c;在计算投影视图矩阵时需要给摄像机输入其位置及转动四元数。这两个量一般通过鼠标键盘来控制&#xff0c;从而达到控制摄像机的目的。本文分析一下其控制原理。 Overload的摄像机控制实现在类CameraController中&#xff0c;其有三个个…...

VScode运行SVN拉下来的项目

安装依赖包 pnpm install 启动程序 查看package.json文件中的serve&#xff0c;根据这个启动 pnpm dev 在浏览器使用http://localhost:8848/访问...

jmeter集成kafka测试

Kafka的使用 查看kafka的topic ./kafka-topics --bootstrap-server 10.1.9.84:9092 --list 查看topic信息 ./kafka-topics --bootstrap-server 10.1.9.84:9092 --describe --topic topic_example_1 创建topic 创建topic名为test&#xff0c;分区数为8&#xff0c;副本数为…...

Java面试题-UDP\TCP\HTTP

UDP UDP特性 &#xff08;1&#xff09;UDP是无连接的&#xff1a;发送数据之前不需要像TCP一样建立连接&#xff0c;也不需要释放连接&#xff0c;所以减少了发送和接收数据的开销 &#xff08;2&#xff09;UDP 使用尽最大努力交付&#xff1a;即不保证可靠交付 &#xff0…...

使用WPF模仿Windows记事本界面

本次仅模仿Windows记事本的模样&#xff0c;并未实现其功能。 所有代码如下&#xff1a; <Window x:Class"控件的基础使用.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/…...

【目标检测】Co-DETR:ATSS+Faster RCNN+DETR协作的先进检测器(ICCV 2023)

论文&#xff1a;DETRs with Collaborative Hybrid Assignments Training 代码**&#xff1a;https://github.com/Sense-X/Co-DETR 文章目录 摘要一、简介二、本文方法2.1.概述2.2.协同混合分配训练2.3. 定制的正 Query 生成2.4. Co-DETR为何有效1、丰富编码器的监督2、通过减少…...

iOS QQ登录SDK升级后报错Duplicate interface definition for class ‘TencentOAuth‘修复

起因 最近发现QQ登录SDK sdk-Lite3.3.8 TencentOpenAPI 在部分手机上会崩溃到初始化位置_tencentOAuth [[TencentOAuth alloc] initWithAppId:appid andDelegate:self];&#xff0c; 比如&#xff1a;iPhone6p 版本12.5.4&#xff0c;iPhone8p 版本14.1&#xff0c;iPad版本1…...

laravel中锁以及事务的简单使用

一、首先来说一下什么是共享锁&#xff1f;什么是排他锁&#xff1f; 共享&#xff1a;我可以读 写 加锁 , 别人可以 读 加锁。 排他&#xff1a;只有我 才 可以 读 写 加锁 , 也就是说&#xff0c;必须要等我提交事务&#xff0c;其他的才可以操作。 二、简单例子实现加锁 锁…...

Vue+openlayers+projs4实现坐标转换

一、背景 有一堆点数据&#xff0c;需要在地图上标记&#xff0c;只知参考北京54坐标系或西安80坐标系&#xff0c;但具体是哪种不清楚&#xff0c;这时候就需要坐标转换。ps&#xff1a;EPSG&#xff1a;3857&#xff08;openlayers参照的坐标系&#xff09; 二、思路 1、研…...

09 创建型模式-建造者模式

1.建造者模式介绍&#xff1a; 建造者模式 (builder pattern), 也被称为生成器模式 , 是一种创建型设计模式 定义: 将一个复杂对象的构建与表示分离&#xff0c;使得同样的构建过程可以创建不 同的表示。 2.建造者模式要解决的问题 建造者模式可以将部件和其组装过程分开&am…...

4.9 多协议标记交换MPLS

思维导图&#xff1a; 前言&#xff1a; **4.9 多协议标记交换MPLS笔记** 1. **定义与背景**&#xff1a; - MPLS (多协议标记交换) 是一种由 IETF 开发的新协议。 - “多协议”意味着 MPLS 的上层可以使用多种协议。 - 该协议综合了多家公司的技术&#xff0c;如 C…...

label-studio的使用教程(导入本地路径)

文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...

基于FPGA的PID算法学习———实现PID比例控制算法

基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容&#xff1a;参考网站&#xff1a; PID算法控制 PID即&#xff1a;Proportional&#xff08;比例&#xff09;、Integral&#xff08;积分&…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

【C语言练习】080. 使用C语言实现简单的数据库操作

080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...

拉力测试cuda pytorch 把 4070显卡拉满

import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试&#xff0c;通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小&#xff0c;增大可提高计算复杂度duration: 测试持续时间&#xff08;秒&…...

Java面试专项一-准备篇

一、企业简历筛选规则 一般企业的简历筛选流程&#xff1a;首先由HR先筛选一部分简历后&#xff0c;在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如&#xff1a;Boss直聘&#xff08;招聘方平台&#xff09; 直接按照条件进行筛选 例如&#xff1a…...

Element Plus 表单(el-form)中关于正整数输入的校验规则

目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入&#xff08;联动&#xff09;2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...

rnn判断string中第一次出现a的下标

# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...

return this;返回的是谁

一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请&#xff0c;不同级别的经理有不同的审批权限&#xff1a; // 抽象处理者&#xff1a;审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

Java编程之桥接模式

定义 桥接模式&#xff08;Bridge Pattern&#xff09;属于结构型设计模式&#xff0c;它的核心意图是将抽象部分与实现部分分离&#xff0c;使它们可以独立地变化。这种模式通过组合关系来替代继承关系&#xff0c;从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...