多态(难的起飞)

news/2024/7/20 17:00:50/文章来源:https://blog.csdn.net/2303_76260560/article/details/139078084

注意   virtual关键字:

    1、可以修饰原函数,为了完成虚函数的重写,满足多态的条件之一

   2、可以菱形继承中,去完成虚继承,解决数据冗余和二义性

两个地方使用了同一个关键字,但是它们互相一点关系都没有

虚函数重写:

 

多态的条件:

1、虚函数的重写

2、父类对象的指针或者引用去调用虚函数

必须是父类指针或者引用

不可以是子类因为父类不可以传给子类

class Person
{
public:virtual void BuyTicket() { cout << "Person全票" << endl; }
};
class Student : public Person
{
public:virtual void BuyTicket() { cout << "Student半票" << endl; }
};
void func(Person& p1)
{p1.BuyTicket();
}
int main()
{Person p1;Student s1;func(p1);func(s1);return 0;
}

协变(是多态的一种特殊情况):

多态:

1、虚函数的重写(必须要函数名、返回值、参数要相同)

2、父类对象的指针或者引用去调用虚函数

但是协变可以返回值可以不同

但是返回值必须是基类的指针或引用和子类的指针或引用

//class A
//{
//};
//class B :public A
//{
//}
//其他类的基类和派生类也可以
//class Person
//{
//public:
//	virtual A* BuyTicket() { cout << "Person全票" << endl; return nullptr; }
//};
//class Student : public Person
//{
//public:
//	virtual B* BuyTicket() { cout << "Student半票" << endl;  return nullptr; }
//};
//void func(Person& p1)
//{
//	p1.BuyTicket();
//}
//class Person
{
public:virtual Person* BuyTicket() { cout << "Person全票" << endl; return nullptr; }
};
class Student : public Person
{
public:virtual Student* BuyTicket() { cout << "Student半票" << endl;  return nullptr;}
};
void func(Person& p1)
{p1.BuyTicket();
}
int main()
{Person p1;Student s1;func(p1);func(s1);return 0;
}

析构函数:

面试题:析构函数需不需要加vitrual?

class Person
{
public:~Person() { cout << "~Person()" << endl; }};
class Student : public Person
{
public:~Student() { cout << "~Student()" << endl;}
};int main()
{Person* p1= new Student;delete p1;return 0;
}

这种情况下父类的指针指向了new Student 但是使用完会造成内存泄漏,父类的指针只会调用父类的析构函数去清理该指向部分的空间,但是我们需要清理子类的空间就要调用子类的析构函数,所以需要加virtual 构成虚函数的重写,让父类的指针调用构成多态,就可以调用子类的析构函数。

 

看下一道面试题:

在做面试题之前先看下面代码

在继承关系中,

如何理解上述话呢?

看下面代码

在满足多态的条件下,虚函数的继承是继承了接口,所以缺省值继承了,但是子类要自己重写实现

所以当父类中的有虚函数,子类的就可以不用加virtual,但是不规范

答案:是B

为什么多态就要继承父类的接口?突然感悟

比喻:子类中的函数 drive(Banz* const this),父类也有(Car* const this)
  //子类这个this是接收不了父类的指针,只有父类的指针或引用才可以指向子类
   //所以这个继承接口才需要继承父类的接口----突然感悟

============下面代码=============== 

//作者:蚂蚁捉虫虫
//链接:https ://www.zhihu.com/question/517444641/answer/2390138862
//来源:知乎
//著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
#include <iostream>       // std::cout
class Base {public:Base() {};virtual void func_a(int a = 0) {}; //这个是虚函数,子类只继承接口,具体的实现,由子类去实现void func_b(int b) { std::cout << b + 10 << "\n"; }; //这个是实函数,其接口和实现,都会被子类继承
};class Base_A : public Base {
public:void func_a(int a=15) { std::cout << a << "\n"; };
};class Base_B : public Base {
public:void func_a(int a) { std::cout << a + 15 << "\n"; };
};int main()
{Base_A a;Base_B b;a.func_a(); //仅仅继承了基类的接口,但没有继承实现a.func_b(10); //继承了基类的接口及实现std::cout << std::endl;b.func_a(10); //仅仅继承了基类的接口,但没有继承实现b.func_b(10); //继承了基类的接口及实现return 0;
}

 

只有在满足多态的情况下,虚函数的继承才是父类的虚函数继承对于子类来说继承的是父类的接口(包括缺省值),子类函数的实现需要子类来写

上述代码只是完成了重写,并没有满足多态,所以并没有继承接口

关键字final和override

1、final修饰虚函数,表示该虚函数不能再被继承

也可以修饰class叫最终类不能被继承

override关键字:检查子类的虚函数是否完成重写

构成虚函数重写吗?

没有,认真看,但是不会报错,所以,加上override就可以自动检测检查子类的虚函数是否完成重写

重载、重写、重定义

抽象类

可以看下列代码:

//作者:蚂蚁捉虫虫
//链接:https://www.zhihu.com/question/517444641/answer/2390138862
//来源:知乎
//著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。class Base {public:Base(){};virtual void func_a(int a) = 0; //这个是纯虚函数,子类只继承接口,具体的实现,由子类去实现void func_b(int b) {std::cout << b+10 << "\n";}; //这个是实函数,其接口和实现,都会被子类继承
};class Base_A: public Base{
public:void func_a(int a){std::cout << a << "\n";};
};class Base_B: public Base{
public:void func_a(int a){std::cout << a + 15 << "\n";};
};int main ()
{Base_A a;Base_B b;a.func_a(10); //仅仅继承了基类的接口,但没有继承实现a.func_b(10); //继承了基类的接口及实现std::cout << std::endl;b.func_a(10); //仅仅继承了基类的接口,但没有继承实现b.func_b(10); //继承了基类的接口及实现return 0;
}

上述代码里,定一个基类,里面有两个成员函数,一个是虚函数,一个是实际函数;然后又定义了两个子类,Base_A和Base_B,两个子类对基类中的func_b函数有不一样的实现

纯虚函数的作用强制子类完成重写

表示抽象的类型。抽象就是在现实中没有对应的实体的

接口继承和实现继承

多态的原理:

测试我们发现b对象是8个字节,除了_b成员,还多了一个指针_vfptr放在对象对面,我们叫做虚函数指针我们叫做虚函数表指针。一个含有虚函数表的类中至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表称虚表

注意:虚函数存放在哪里? 虚表存在哪里

虚表存的是虚函数指针,不是虚函数,虚函数也是函数所以也是存在代码区,只是它的地址被存进虚函数指针中,这个指针被虚表记录着

重写:接口继承,实现重写,在原理上是覆盖将父类继承下来的vfptr的父类虚函数的地址覆盖成子类的虚函数地址

从反汇编看原理:

普通类函数:

在编译的过程中就已经确定了调用函数的地址

现在我们加上virtual虚函数

进入汇编,当形成多态时是如何调用的 

00B021E1 8B 45 08             mov         eax,dword ptr [A]  //将A指向空间地址给eax
00B021E4 8B 10                mov         edx,dword ptr [eax]  //将eax空间中的前四个字节地址给edx就是虚函数表指针
00B021E6 8B F4                mov         esi,esp//这个是维护函数栈帧的寄存器,不用管  
00B021E8 8B 4D 08             mov         ecx,dword ptr [A]  //将A指向空间地址给ecx
00B021EB 8B 42 04             mov         eax,dword ptr [edx+4]  //因为edx保存的是前四个字节空间的地址就是虚函数表指针+4就是run()的地址,将run()地址给eax,前4个是speak()的地址
00B021EE FF D0                call        eax //调用run()
00B021F0 3B F4                cmp         esi,esp 
00B021F2 E8 1A F1 FF FF       call        __RTC_CheckEsp (0B01311h) 

 多态就是有virtual函数是用虚函数表指针去存放虚函数的地址,在由虚函数表指针调用对应的函数

面试题:

虚函数存在哪里?代码段,虚函数和普通函数一样都是函数所以都是编译成指令存进代码段中

虚函数表存在哪里?

存在代码段中,不是存在栈区,因为栈区是由一个个栈帧堆建的所以每调用创建一个对象就要建立一个虚表是很消耗内存的

证明一下:

虚表存放在代码区中的代码段最合适,堆区是动态开辟的数据区分为bss区(存放未初始化的static和未初始化的全局变量)数据区存放(存放初始化的static和初始化的全局变量),所以代码段是最合适的

反向验证:

发现很接近代码区

总结:

多态的本质原理,符合多态的两个条件。那么在父类的指针或引用调用时,会到指向对象的虚表找到对应的虚函数地址,进行调用

多态(程序运行时去指向对象的虚表中找到函数地址,进行调用,所以p指向谁就调用谁的虚函数)

普通函数的调用,编译链接时确定函数的地址,运行时直接调用。类型时谁就是谁调用

动态绑定和静态绑定:

编译:就是代码和语法检查其实就是预处理、编译、汇编、链接

运行:就是将可执行文件加载到内存中进行对数据区的数据替换

静态绑定:更具调的类型就确定了调用的函数

动态绑定:运行时具体拿到类型确定程序的具体行为,就是在编译时无法确定函数的行为

运行时根据寄存器去拿到函数的地址

单继承和多继承的虚表(不是虚基表)

单继承:

void(*p)();  //函数指针

补充:

函数名就是函数的地址

那我们手动打印虚函数表

class base
{
public:virtual void func1() { cout << "base::func1()" << endl; }virtual void func2() { cout << "base::func2()" << endl; }};
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; }};
//void(*)()
typedef void(*VF_PTR)();//重命名函数指针void PrintVFTable(VF_PTR* pTable)//VF_PTR pTable[]  函数指针数组==虚函数表指针
{for (size_t i = 0; pTable[i] != 0; i++){printf("pTable[%d]=%p->", i, pTable[i]);VF_PTR f = pTable[i];//得到函数的地址==函数名f();}cout << endl;
}int main()
{base b1;derive d2;PrintVFTable((VF_PTR*)(*(int*)&b1));//取b1的地址因为要取到虚函数表指针,它在对象的前四个字节//所以转换成int*在解引用就是取空间b1的前四个字节,因为此时是int*//所以要转成VF_PTR*PrintVFTable((VF_PTR*)(*(int*)&d2));return 0;
}

多继承的虚表:

计算一下test 对象等于多少?

class base
{
public:virtual void func1() { cout << "base::func1()" << endl; }virtual void func2() { cout << "base::func2()" << endl; }int i = 0;
};
class derive
{
public:virtual void func1() { cout << "derive::func1()" << endl; }virtual void func3() { cout << "derive::func3()" << endl; }virtual void func4() { cout << "derive::func4()" << endl; }int i = 0;
};
class test:public base,public derive
{
public:virtual void func3() { cout << "test::func1()" << endl; }virtual void func2() { cout << "test::func3()" << endl; }virtual void func7() { cout << "test::func4()" << endl; }
public:int i = 0;
};//void(*)()
typedef void(*VF_PTR)();//重命名函数指针void PrintVFTable(VF_PTR* pTable)//VF_PTR pTable[]  函数指针数组==虚函数表指针
{for (size_t i = 0; pTable[i] != 0; i++){printf("pTable[%d]=%p->", i, pTable[i]);VF_PTR f = pTable[i];//得到函数的地址==函数名f();}cout << endl;
}int main()
{test i;cout << sizeof(i) << endl;return 0;
}

等于20   

编译器又没显示!!!那我们手动去看看

继承的子类和其父类的表不是同一张表,只有同一类才是用一张表哦

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

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

相关文章

Python | Leetcode Python题解之第116题填充每个节点的下一个右侧节点指针

题目&#xff1a; 题解&#xff1a; class Solution:def connect(self, root: Node) -> Node:if not root:return root# 从根节点开始leftmost rootwhile leftmost.left:# 遍历这一层节点组织成的链表&#xff0c;为下一层的节点更新 next 指针head leftmostwhile head:#…

JVM-之GC日志

一、 开启gc 日志 在项目中开启GC 日志打印后会查看gc 日志如下 nohup java -Xms768m -Xmx768m -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath./dumplog/dumplog.log -Xloggc:./dumplog/gc.log -XX:PrintGCDetails -XX:PrintGCDateStamps -XX:PrintHeapAtGC -jar xxxx…

PPT大珩助手新功能-生成迷宫

大珩助手是一款功能丰富的办公软件插件&#xff0c;它主要分为两个版本&#xff1a;PPT大珩助手和Word大珩助手。这两个版本都旨在提高用户在处理演示文稿和文档时的效率。 PPT大珩助手 这是一款专门为Microsoft PowerPoint设计的插件。它提供了多种功能&#xff0c;例如素材…

CSS学习笔记:flex布局(弹性布局)

设置flex布局 父元素添加display: flex 使用justify-content调节元素在主轴的对齐方式 给父元素添加justify-content属性&#xff0c;取值如下 用于调节子元素在主轴方向&#xff08;水平方向&#xff09;的对齐方式 使用align-items调节元素在侧轴的对齐方式 给父元素添加…

卸载/删除 Maxask.com,最简单的方法

被绑架的浏览器&#xff0c;太恶心了。 Maxask伪装成了插件&#xff0c;在你搜索网页的时候利用了重定向&#xff0c;导致出现的界面时Maxask的界面&#xff0c;很恶心。 只需要排查正在使用的&#xff0c;如下图有颜色的图表。 删除一个插件&#xff0c;浏览器搜索一下看看有…

数据结构——红黑树

红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但是在每个结点上会多出一个存储位来表示结点的颜色&#xff0c;可以是红色(RED)或者黑色(BLACK),通过任何一条根到叶子的路径上各个结点着色方式的限制&#xff0c;确保红黑树没有一条路径会比其他路径长出两倍&…

前端开发工程师——数据可视化

canvas canvas绘制线段 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthd…

Web安全:SQL注入之时间盲注原理+步骤+实战操作

「作者简介」&#xff1a;2022年北京冬奥会网络安全中国代表队&#xff0c;CSDN Top100&#xff0c;就职奇安信多年&#xff0c;以实战工作为基础对安全知识体系进行总结与归纳&#xff0c;著作适用于快速入门的 《网络安全自学教程》&#xff0c;内容涵盖系统安全、信息收集等…

Golang | Leetcode Golang题解之第116题填充每个节点的下一个右侧节点指针

题目&#xff1a; 题解&#xff1a; func connect(root *Node) *Node {if root nil {return root}// 每次循环从该层的最左侧节点开始for leftmost : root; leftmost.Left ! nil; leftmost leftmost.Left {// 通过 Next 遍历这一层节点&#xff0c;为下一层的节点更新 Next …

帝国CMS验证码不显示怎么回事呢?

帝国CMS验证码有时候会不显示或打叉&#xff0c;总结自己的解决方法。 1、检查服务器是否开启GD库 测试GD库是否开启的方法&#xff1a;浏览器访问&#xff1a;/e/showkey/index.php&#xff0c;如果出现一堆乱码或报错&#xff0c;证明GD库没有开启&#xff0c;开启即可。 2…

深度学习-转置卷积

转置卷积 转置卷积&#xff08;Transposed Convolution&#xff09;&#xff0c;也被称为反卷积&#xff08;Deconvolution&#xff09;&#xff0c;是深度学习中的一种操作&#xff0c;特别是在卷积神经网络&#xff08;CNN&#xff09;中。它可以将一个低维度的特征图&#x…

echarts学习篇

一、使用echarts 1.引入 Apache ECharts <!DOCTYPE html> <html> <head> <meta charset"utf-8" /> <!-- 引入刚刚下载的 ECharts 文件 --> <script src"echarts.js"></script> </head> </html> 2.…

数据结构---链表

1、链表分类 对于链表可以分为几种&#xff0c;不同的分类对应不同的应用场景&#xff0c;其中&#xff0c;双向循环链表和单向链表最常用。 1.1链表按照有头无头分类 也就是说有无哨兵位&#xff0c;哨兵位&#xff0c;是一个开辟的空间&#xff0c;但是不放置数据&#xf…

AURIX TC3xx单片机介绍-启动过程介绍1

从各个域控制器硬件解决方案来看,MPU可能来自多个供应商,有瑞萨,有NXP等,但对于MCU来说,基本都采用英飞凌TC3xx。 今天我们就来看一下TC3xx的启动过程,主要包含如下内容: uC上电过程中,会经过一个上电时序,从复位状态“脱离”出来;Boot Firmware是复位后第一个执行的…

金职优学:分析央国企面试如何通关?

在当今竞争激烈的就业市场中&#xff0c;中央和国有企业&#xff08;以下简称“央国企”&#xff09;的面试机会对求职者来说是非常有吸引力的。这些企业通常拥有稳定的发展前景、良好的薪酬福利和广阔的职业发展空间。但是&#xff0c;要想成功通过央国企的面试&#xff0c;求…

YOLOv8+PyQt5西红柿成熟度检测系统完整资源集合(yolov8模型,从图像、视频和摄像头三种路径识别检测,包含登陆页面、注册页面和检测页面)

西红柿成熟度检测&#xff08;https://mbd.pub/o/bread/mbd-ZpWbk5ly&#xff09;_哔哩哔哩_bilibili 资源包含可视化的西红柿成熟度检测系统&#xff0c;基于最新的YOLOv8训练的西红柿成熟度检测模型&#xff0c;和基于PyQt5制作的可视化西红柿成熟度检测系统&#xff0c;包含…

加速模型训练 GPU cudnn

GPU的使用 在定义模型时&#xff0c;如果没有特定的GPU设置&#xff0c;会使用 torch.nn.DataParallel 将模型并行化&#xff0c;充分利用多GPU的性能&#xff0c;这在加速训练上有显著影响。 model torch.nn.DataParallel(model).cuda() cudnn 的配置&#xff1a; cudnn.…

Habicht定理中有关子结式命题3.4.6的证明

个人认为红色区域有问题&#xff0c;因为 deg ⁡ ( ϕ ( S j ) ) r \deg{\left( \phi\left( S_{j} \right) \right) r} deg(ϕ(Sj​))r&#xff0c;当 i ≥ r i \geq r i≥r时&#xff0c; s u b r e s i ( ϕ ( S j 1 ) , ϕ ( S j ) ) subres_{i}\left( \phi(S_{j 1}),\p…

景源畅信电商:做抖店的成本高吗?

在当今数字化时代&#xff0c;抖音不仅仅是一个分享短视频的平台&#xff0c;更是一个充满商机的市场。随着抖音用户量的激增&#xff0c;越来越多的商家和个人开始关注在抖音上开设店铺&#xff0c;即所谓的“抖店”。那么&#xff0c;做抖店的成本高吗?这个问题困扰着许多初…

《python编程从入门到实践》day40

# 昨日知识点回顾 编辑条目及创建用户账户 暂没能解决bug&#xff1a; The view learning_logs.views.edit_entry didnt return an HttpResponse object. It returned None instead.# 今日知识点学习 19.2.5 注销 提供让用户注销的途径 1.在base.html中添加注销链接 …