C++多态的实现原理

【欢迎关注编码小哥,学习更多实用的编程方法和技巧】

1、类的继承

子类对象在创建时会首先调用父类的构造函数

父类构造函数执行结束后,执行子类的构造函数

当父类的构造函数有参数时,需要在子类的初始化列表中显式调用

Child(int i) : Parent("在子类构造函数中的初始化列表进行显式调用父类构造函数")

析构函数调用的先后顺序与构造函数相反

继承与组合的混搭

类中的成员变量可以是其它类的对象(组合)

口诀:先父母,后客人,再自己。

当子类中定义的成员变量与父类中的成员变量同名时

子类依然从父类继承同名成员

在子类中通过作用域分别符::进行同名成员区分

同名成员存储在内存中的不同位置

cout << "Parent::i = " << Parent::i << endl;

cout << "Child::i = " << Child::i << endl;

cout << "Parent::f = " << Parent::f << endl;

子类对象可以当作父类对象使用

子类对象在创建时需要调用父类构造函数进行初始化

子类对象在销毁时需要调用父类析构函数进行清理

先执行父类构造函数,再执行成员构造函数

在继承中的析构顺序与构造顺序对称相反

同名成员通过作用域分辨符进行区分

2、函数的重写

父类中被重写的函数依然会继承给子类

默认情况下子类中重写的函数将隐藏父类中的函数

通过作用域分辨符::可以访问到父类中被隐藏的函数

Parent *p = &child;

p->print();

Parent& rP = child;

rP.print();

打印输出的是父类的函数。

C++编译器支持静态联编,就是在编译阶段就可以确定下来的多态。

对于动态联编,只有在运行时才能确定的对象类型,编译器是不能作判断的,最稳妥的做法就是使用父类的类型进行操作。

C++与C相同,是静态编译型语言

在编译时,编译器自动根据指针的类型判断指向的是一个什么样的对象

所以编译器认为父类指针指向的是父类对象(根据赋值兼容性原则,这个假设合理)

由于程序没有运行,所以不可能知道父类指针指向的具体是父类对象还是子类对象

从程序安全的角度,编译器假设父类指针只指向父类对象,因此编译的结果为调用父类的成员函数

面向对象的新需求

根据实际的对象类型来判断重写函数的调用

如果父类指针指向的是父类对象则调用父类中定义的函数

如果父类指针指向的是子类对象则调用子类中定义的重写函数

实现了以上的功能就是面向对象中的多态。

多态

根据运行时实际的对象类型表现出不同的行为状态,叫多态。

C++中通过virtual关键字对多态进行支持

使用virtual声明的函数被重写后即可展现多态特性

虚函数

在父类函数声明前面加上virtual关键字,使其成为虚函数。这时,函数就表现出多态性。

函数重载

必须在同一个类中进行

子类无法重载父类的函数,父类同名函数将被覆盖

重载是在编译期间根据参数类型和个数决定调用函数

函数重写

必须发生于父类与子类之间

并且父类与子类中的函数必须有完全相同的原型

使用virtual声明之后能够产生多态,子类在重写时自动成为虚函数

多态是在运行期间根据具体对象的类型决定调用函数

隐藏:

派生类中的函数与基类中的函数同名并且参数相同,但基类函数不是虚函数

派生类中的函数与基类中的函数同名,参数不同,不管基类函数是否是虚函数,基类函数都会被屏蔽。

child.Parent::func(); //使用作用域分别符可以调用

child.func(); //不可直接调用,因为父类的同名函数被隐藏。

3、C++中多态的实现原理

当类中声明虚函数时,编译器会在类中生成一个虚函数表

虚函数表是一个存储类成员函数指针的数据结构

虚函数表是由编译器自动生成与维护的

virtual成员函数会被编译器放入虚函数表中

存在虚函数时,每个对象中都有一个指向虚函数表的指针VPTR

每一个类都会由编译器自动生成一个虚函数表,而且每个类只有唯一一个表。此类生成的每一个对象里面都隐含着一个指向该表的指针。

调用虚函数时,会通过VPTR指针指向的虚函数表中查询该函数,找到入口地址,并调用。这个过程相对比较耗时,因此执行效率相对比较低,因此,没有必要把所有的函数都设计为虚函数。

对象在创建的时候由编译器对VPTR指针进行初始化

只有当对象的构造完全结束后VPTR的指向才最终确定

父类对象的VPTR指向父类虚函数表

子类对象的VPTR指向子类虚函数表

构造函数中调用虚函数无法实现多态。

纯虚函数

面向对象中的抽象类

抽象类可用于表示现实世界中的抽象概念

抽象类是一种只能定义类型,而不能产生对象的类

抽象类只能被继承并重写相关函数

抽象类的直接特征是纯虚函数

纯虚函数是只声明函数原型,而故意不定义函数体的虚函数。

统一的格式:

virtual 返回类型 函数名(参数列表)= 0;

抽象类与纯虚函数

包含着纯虚函数的类叫抽象类

抽象类不能用于定义对象

抽象类只能用于定义指针和引用

抽象中的纯虚函数必须被子类重写

class Shape{public:virtual double area() = 0;};class Rectangle : public Shape{public:Rectangle(double a, double b){m_a = a;m_b = b;}double area(){return m_a * m_b;}private:double m_a;double m_b;};class Circle : public Shape{private:double m_r;public:Circle(double r){m_r = r;}double area(){return 3.14 * m_r * m_r;}};void func(Shape *s){cout << s->area() << endl;}int main(int argc, char *argv[]){Rectangle rect(3,2);Circle c(4);func(&rect);func(&c);return EXIT_SUCCESS;}多态与数组class Parent{protected:int i;public:virtual void func(){cout << "Parent::func()" << endl;}};class Child : public Parent{protected:int j;public:Child(int a, int b){i = a;j = b;}void func(){cout << "i = " << i << " , j = " << j << endl;}};int main(int argc, char *argv[]){Parent *pp = NULL;Child *pc = NULL;Child ca[] = {Child(1,2),Child(3,4),Child(5,6),Child(7,8)};pp = ca;pc = ca;cout << "sizeof(Parent) = " << sizeof(Parent) << endl;cout << "sizeof(Child) = " << sizeof(Child) << endl;cout << setbase(16) << "pp = " << pp << endl;cout << setbase(16) << "pc = " << pc << endl;pp->func();pc->func();pp++;pc++;cout << setbase(16) << "pp = " << pp << endl;cout << setbase(16) << "pc = " << pc << endl;//pp->func();//pc->func();return EXIT_SUCCESS;}

注:Parent类对象占有8个字节,因为需要维护一个虚函数表的指针也占4个字节。同样的,Child对象除自身的变量j以外,还要从父类继承一个变量i,也要维护一个虚函数表的指针,一共12个字节。

不要将多态应用于数组

指针运算是通过指针的类型进行的 (编译时确定)

多态是通过虚函数表实现的 (运行时确定)

虚基类及多重继承

被实际开发经验抛弃的多继承

工程开发中真正意义上的多继承是几乎不被使用的

多重继承带来的代码复杂性远多于其带来的便利

多重继承对代码维护性上的影响是灾难性的

在设计方法上,任何多继承都可以用单继承代替

为了解决从不同途径继承来的同名数据成员造成的二义性问题 , 可以将共同基类设置为虚基类 。 这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝。

class B : virtual public A

{

};

class C : virtual public A

{

};

这就是虚基类的来源。

C++的接口设计

实际工程经验证明

多重继承接口不会带来二义性和复杂性问题

多重继承可以通过精心设计用单继承和接口代替

接口类只是一个功能说明,而不是功能实现 。

子类需要根据功能说明定义功能实现 。

绝大多数面向对象语言都不支持多继承,但都支持接口的概念

C++中没有接口的概念

C++中可以使用纯虚函数实现接口

class Interface

{

public:

virtual void func1() = 0;

virtual void func2(int i) = 0;

virtual void func3(int i, int j) = 0;

};

接口类中只有函数原型的定义,没有任何数据的定义。

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

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

相关文章

JS听到了双生花的回响

日期对象 学会了日期对象可以让网页显示日期 是用来表示时间的对象&#xff0c;可以得到当前系统的时间 实例化 new关键字&#xff0c;就是实例化的代表 就比如说&#xff0c;你没有对象&#xff0c;但是你是程序员&#xff0c;这个时候你可以先定义一个类&#xff08;你的…

C++类中多线程的编码方式

问题 在C++代码中,一般的代码是需要封装在类里面,比如对象,方法等。否则就不能很好的利用C++面向对象的能力了。 但是这个方式在处理线程时会碰到一个问题。 考虑下面一个简单的场景: class demoC { public:std::thread t;int x;void threadFunc(){std::cout<<x&…

Chapter 17 v-model进阶

欢迎大家订阅【Vue2Vue3】入门到实践 专栏&#xff0c;开启你的 Vue 学习之旅&#xff01; 文章目录 1 v-model原理2 表单类组件封装3 v-model简化代码 1 v-model原理 1. 基本原理 v-model 本质上是一个语法糖&#xff0c;它将 value 属性 和 input 事件 的绑定合并为一个指令…

spring-boot-maven-plugin 标红

情况&#xff1a;创建好 Spring Boot 项目后&#xff0c;pom.xml 文件中 spring-boot-maven-plugin 标红。 解决方案&#xff1a;加上 Spring Boot 的版本即可解决。

电子应用设计方案-31:智能AI音响系统方案设计

智能 AI 音响系统方案设计 一、引言 智能 AI 音响作为一种新兴的智能家居设备&#xff0c;通过融合语音识别、自然语言处理、音频播放等技术&#xff0c;为用户提供便捷的语音交互服务和高品质的音乐体验。本方案旨在设计一款功能强大、性能稳定、用户体验良好的智能 AI 音响系…

“harmony”整合不同平台的单细胞数据之旅

其实在Seurat v3官方网站的Vignettes中就曾见过该算法&#xff0c;但并没有太多关注&#xff0c;直到看了北大张泽民团队在2019年10月31日发表于Cell的《Landscap and Dynamics of Single Immune Cells in Hepatocellular Carcinoma》&#xff0c;为了同时整合两类数据&#xf…

接口测试工具:reqable

背景 在众多接口测试工具中挑选出一个比较好用的接口测试工具。使用过很多工具&#xff0c;如Postman、Apifox、ApiPost等&#xff0c;基本上是同类产品&#xff0c;一般主要使用到的功能就是API接口和cURL&#xff0c;其他的功能目前还暂未使用到。 对比 性能方面&#xff…

内容安全与系统构建加速,助力解决生成式AI时代的双重挑战

内容安全与系统构建加速&#xff0c;助力解决生成式AI时代的双重挑战 0. 前言1. PRCV 20241.1 大会简介1.2 生成式 Al 时代的内容安全与系统构建加速 2. 生成式 AI2.1 生成模型2.2 生成模型与判别模型的区别2.3 生成模型的发展 3. GAI 内容安全3.1 GAI 时代内容安全挑战3.2 图像…

SRS搭建直播推流服务

学习链接 5分钟教你搭建SRS流媒体服务器 - B站视频 SRS Stack 入门B站合集视频 - SRS官方教程 SRS官网 SRS官网文档 ossrs/srs github SRS for window - 可以安装windows版本的srs&#xff0c;SRS 5.0.89正式支持Windows&#xff0c;每个5.0的版本都会提供安装包 文章目录…

javaScript数据类型存储

2.1、简单类型与复杂类型 简单类型又叫做基本数据类型或者值类型&#xff0c;复杂类型又叫做引用类型 值类型&#xff1a;简单数据类型/基本数据类型&#xff0c;在存储时变量中存储的时值本身&#xff0c;因此叫做值类型 string、number、boolean、undefined、null 注意&…

深度学习之 DenseNet和2图像分割常用数据集

1 DenseNet 卷积神经网络结构的设计主要朝着两个方向发展&#xff0c;一个是更宽的网络&#xff08;代表&#xff1a;GoogleNet、VGG&#xff09;&#xff0c;一个是更深的网络&#xff08;代表&#xff1a;ResNet&#xff09;。但是随着层数的加深会出现一个问题——梯度消失&…

Nginx:反向代理

目录 反向代理原理 反向代理配置 日志对比 反向代理原理 网站通过代理服务器发布&#xff0c;用户无需得知网站的实际地址&#xff0c;通过代理服务器进行请求与响应。 用户所有的网站请求报文与响应报文都被代理服务器拦截&#xff0c;在网络层将源地址和目的地址进行了修改…

Linux系统编程——进程替换

目录 前言 二、进程程序替换的概念 三、进程程序替换的原理 ​编辑 四、为什么需要进行进程程序替换 五、如何进行进程程序替换 1、进程替换函数&#xff1a; 1)execl()函数 2)execv()函数 3) execlp()函数 4) execvp()函数 5&#xff09;execle函数 6&#xff09;ex…

探索HarmonyOS:一键掌握Router与NavPathStatck的传参和页面回调技巧

路由的选择 HarmonyOS提供两种路由实现的方式&#xff0c;分别是 Router 和 NavPatchStack。两者使用场景和特效各有优劣。 组件适用场景特点备注Router模块间与模块内页面切换通过每个页面的url实现模块间解耦NavPathStack模块内页面切换通过组件级路由统一路由管理 什么时候使…

go使用mysql实现增删改查操作

1、安装MySQL驱动 go get -u github.com/go-sql-driver/mysql2、go连接MySQL import ("database/sql""log"_ "github.com/go-sql-driver/mysql" // 导入 mysql 驱动 )type Users struct {ID intName stringEmail string }var db *sql.DBfu…

ffmpeg安装(windows)

ffmpeg安装-windows 前言ffmpeg安装路径安装说明 前言 ffmpeg的安装也是开箱即用的,并没有小码哥说的那么难 ffmpeg安装路径 这就下载好了! 安装说明 将上面的bin目录加入到环境变量,然后在cmd中测试一下: C:\Users\12114\Desktop\test\TaskmgrPlayer\x64\Debug>ffmpe…

FPGA存在的意义:为什么adc连续采样需要fpga来做,而不会直接用iic来实现

FPGA存在的意义&#xff1a;为什么adc连续采样需要fpga来做&#xff0c;而不会直接用iic来实现 原因ADS111x连续采样实现连续采样功能说明iic读取adc的数据速率 VS adc连续采样的速率adc连续采样的速率iic读取adc的数据速率结论分析 FPGA读取adc数据问题一&#xff1a;读取adc数…

《Vue零基础入门教程》第十四课:列表渲染

往期内容 《Vue零基础入门教程》第六课&#xff1a;基本选项 《Vue零基础入门教程》第八课&#xff1a;模板语法 《Vue零基础入门教程》第九课&#xff1a;插值语法细节 《Vue零基础入门教程》第十课&#xff1a;属性绑定指令 《Vue零基础入门教程》第十一课&#xff1a;事…

Redis主从架构

Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的、高性能的键值对存储系统&#xff0c;广泛应用于缓存、消息队列、实时分析等场景。为了提高系统的可用性、可靠性和读写性能&#xff0c;Redis提供了主从复制&#xff08;Master-Slave Replication&#xf…

学成在线day08

部署静态页面 相关操作&#xff1a;https://mx67xggunk5.feishu.cn/wiki/FLozwxrrxihTJbkyTHgchDt4nUc nginx的最终配置文件&#xff1a; worker_processes 1; events {worker_connections 1024; } http {include mime.types;default_type application/octet-strea…