| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> C++进阶 多态原理 -> 正文阅读 |
|
[C++知识库]C++进阶 多态原理 |
多态的原理虚函数表
有些同学可能看到这里会想到我们之前学习的类的大小计算 成员函数在公共区域 所以不算是类的大小 这里大小是四个字节 要是这样想 就被这个题目带进坑里面去了 我们先来看一下啊实际的大小是多少 实际大小是八 这就说明虚函数这里肯定是出问题了 那么问题出在哪里呢? 实际上 因为有虚函数的存在 它在内存中的分布应该是这样子的 指针的大小在32位的系统下是四个字节 因此我们最后算出来的结果才会是8 虚表指针指向一个虚函数表,简称虚表,每一个含有虚函数的类中都至少有一个虚表指针。
我们写出下面的代码 设置三个对照组来对比一下
我们可以将它们抽象成下面的图
由于child对于func2重写了 所以说两个虚表指针指向的虚表中 func2的地址不一样 func1的地址则相同(因为没有被重写) 而由于func3根本不是虚函数 所以说地址不会在虚表中 因为最后我们将func2的地址覆盖掉了 这也就是为什么我们原理层叫做覆盖 语法层叫做重写的原因 注意点: 我们一般会在虚表指针数组的最后放置一个空指针(nullptr) 那么我们在这里总结下 派生类虚表的生成步骤如下
那么接下来的问题又来了 虚表是什么阶段初始化的?虚函数存在哪里?虚表存在哪里?
所以说我们虚表当中存放的地址并不是虚函数 而是指向虚函数的指针
运行结果如下
多态的原理
还是一样 我们先来写代码
学完了上面的知识我们大概就能明白 由pptr和cptr指针找到的函数地址(再虚表中) 是不一样的函数 因此 在不同对象去完成同一行为的时候发生了多态的现象 为什么对象不能构成多态还记不记得我们之前在继承章节学过一个行为叫做切片 当我们使用指针或者引用切片的时候 我们本质上得到的是子类从父类派生过去的那一部分 而有了虚函数之后本质上就是前面多了一个虚表指针 也就是说 我们切片后的指针还有引用都还是使用的子类的虚表指针 但是如果是对象的切片呢? 这里实际上经历了一个拷贝构造的过程 构造出来的person对象 本质上是一个父类对象 所以说它的虚表指针也就是父类的虚表指针 自然无法构成多态 这里总结下:
动态绑定和静态绑定静态绑定: 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也成为静态多态,比如:函数重载。 动态绑定: 动态绑定又称为后期绑定(晚绑定),在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。 接下来我们通过汇编代码 来对其进行进一步的深入了解 首先 我们先验证下 是不是对象的确不能构成多态
我们可以看到它的汇编代码是这样子的
但是如果我们使用引用或者指针来调用 像这样
我们再转到汇编代码 可以看到这样几行汇编
这样就很好的体现了静态绑定是在编译时确定的,而动态绑定是在运行时确定的 继承多态面试题概念题
A.继承?B.封装?C.多态?D.抽象 这个很显然 答案是A 继承 不用过多讲解
A.继承?B.模板?C.对象的自身引用?D.动态绑定 本质上是多态 也就是动态绑定 这题选D
A.继承允许我们覆盖重写父类的实现细节,父类的实现对于子类是可见的,是一种静态复用,也称为白盒复用。 很明显 我们优先使用组合 而不是继承 所以C选项明显错误
A.声明纯虚函数的类不能实例化对象 这里考察的是纯虚函数的概念 答案是A
A.派生类的虚函数与基类的虚函数具有不同的参数个数和类型 这题很明显选B A C D都有明显的错误
A.一个类只能有一张虚表 A选项显然是错误的 父类既可以有父类的虚表也可以有子类的虚表 B显然也是错误的 子类基类不共用 C 虚表并不是动态产生的 它存在于代码段中 D D选项正确
A.A类对象的前4个字节存储虚表地址,B类对象的前4个字节不是虚表地址 这一题考察的是虚表的概念 以及储存位置 答案是D
A.class A?class B?class C?class D 这一题实际上是考察的继承的概念 先构造父类 再构造子类 和先析构子类再析构父类相反 所以说本题选A
A.p1 == p2 == p3 答案是C 有关于指针偏移的问题 指针偏向于先继承的那个父类
A.A->0?B.B->1?C.A->1?D.B->0 这一题考察的是缺省值的问题 记住结论就好 使用的是父类中的缺省值 问答题
多态是指不同继承关系的类对象,去调用同一函数,产生了不同的行为。多态又分为静态的多态和动态的多态。
重载是指两个函数在同一作用域,这两个函数的函数名相同,参数不同。 重写(覆盖)是指两个函数分别在基类和派生类的作用域,这两个函数的函数名、参数、返回值都必须相同(协变例外),且这两个函数都是虚函数。 重定义(隐藏)是指两个函数分别在基类和派生类的作用域,这两个函数的函数名相同。若两个基类和派生类的同名函数不构成重写就是重定义。
构成多态的父类对象和子类对象的成员当中都包含一个虚表指针,这个虚表指针指向一个虚表,虚表当中存储的是该类对应的虚函数地址。因此,当父类指针指向父类对象时,通过父类指针找到虚表指针,然后在虚表当中找到的就是父类当中对应的虚函数;当父类指针指向子类对象时,通过父类指针找到虚表指针,然后在虚表当中找到的就是子类当中对应的虚函数。
我们知道内联函数是会在调用的地方展开的,也就是说内联函数是没有地址的,但是内联函数是可以定义成虚函数的,当我们把内联函数定义虚函数后,编译器就忽略了该函数的内联属性,这个函数就不再是内联函数了,因为需要将虚函数的地址放到虚表中去。
构造函数不能是虚函数,因为对象中的虚表指针是在构造函数初始化列表阶段才初始化的
析构函数可以是虚函数,并且最后把基类的析构函数定义成虚函数。若是我们分别new一个父类对象和一个子类对象,并均用父类指针指向它们,当我们使用delete调用析构函数并释放对象空间时,只有当父类的析构函数是虚函数的情况下,才能正确调用父类和子类的析构函数分别对父类和子类对象进行析构,否则当我们使用父类指针delete对象时,只能调用到父类的析构函数
对象访问普通函数比访问虚函数更快,若我们访问的是一个普通函数,那直接访问就行了,但当我们访问的是虚函数时,我们需要先找到虚表指针,然后在虚表当中找到对应的虚函数,最后才能调用到虚函数。
虚表是在构造函数初始化列表阶段进行初始化的,虚表一般情况下是存在代码段(常量区)的。
菱形虚拟继承因为子类对象当中会有两份父类的成员,因此会导致数据冗余和二义性的问题。
抽象类很好的体现了虚函数的继承是一种接口继承,强制子类去抽象纯虚函数,因为子类若是不抽象从父类继承下来的纯虚函数,那么子类也是抽象类也不能实例化出对象。 其次,抽象类可以很好的去表示现实世界中没有示例对象对应的抽象类型,比如:植物、人、动物等。 总结
|
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/22 19:25:08- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |