【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.mfbz.cn/a/501251.html

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

相关文章

vivado 生成比特流或器件镜像

在生成比特流或器件镜像之前 &#xff0c; 请复查其设置 &#xff0c; 确保这些设置对于您的设计都正确无误 &#xff0c; 这一点至关重要。 Vivado IDE 中的比特流和器件镜像设置分为 2 种类型 &#xff1a; 1. 比特流或器件镜像文件格式设置。 2. 器件配置设置。 在 V…

大数据-hive,初步了解

1. Hive是什么 Hive是基于Hadoop的数据仓库解决方案。由于Hadoop本身在数据存储和计算方面有很好的可扩展性和高容错性&#xff0c;因此使用Hive构建的数据仓库也秉承了这些特性。 简单来说&#xff0c;Hive就是在Hadoop上架了一层SQL接口&#xff0c;可以将SQL翻译成MapRedu…

java题目9:100匹马驮100担货,大马一匹驮3担,中马一匹驮2担,小马两匹驮1担。计算大中小马的数目(HorsesPackGoods9)

每日小语 正是他的意图损坏了他的悟性。——《充足理由律的四重根》 思考 有点鸡兔同笼的感觉嗷&#xff0c; //100匹马驮100担货&#xff0c;大马一匹驮3担&#xff0c;中马一匹驮2担&#xff0c;小马两匹驮1担。计算大中小马的数目&#xff08;public class HorsesPackGoo…

Centos7安装RTL8111网卡驱动

方法一&#xff1a; // 安装pciutils # yum install -y pciutils // 查看pci设备信息 # lspci | grep -i Ethernet 09:00.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller (rev 03) // 上面看到是Re…

使用倒模耳机壳UV树脂胶液制作HIFI耳机隔音降噪耳机壳推荐的材料和工艺流程?

对于使用倒模耳机壳UV树脂胶液制作HIFI耳机隔音降噪耳机壳&#xff0c;以下是一些推荐的材料和工艺流程&#xff1a; 材料&#xff1a; UV树脂胶液&#xff1a;选择适合倒模工艺的UV树脂胶液&#xff0c;要求具有高透明度、良好的流动性和固化性能。模具材料&#xff1a;根据…

RWKV_Pytorch:支持多硬件适配的开源大语言模型推理框架

亲爱的技术探索者们&#xff0c;今天我要向大家隆重推荐一个在开源社区中崭露头角的项目——RWKV_Pytorch。这是一个基于Pytorch的RWKV大语言模型推理框架&#xff0c;它不仅具备高效的原生Pytorch实现&#xff0c;而且还扩展了对多种硬件的适配支持&#xff0c;让模型的部署和…

JUC/多线程 模式(四)

一、同步模式之保护性暂停 即 Guarded Suspension &#xff0c;用在一个线程等待另一个线程的执行结果 产生结果的线程和使用结果的线程是一一对应的&#xff0c;有多少个生产结果的线程就有多少个使用结果的线程。 要点 有一个结果需要从一个线程传递到另一个线程&#xff0…

C#_事件_多线程(基础)

文章目录 事件通过事件使用委托 多线程(基础)进程:线程: 多线程线程生命周期主线程Thread 类中的属性和方法创建线程管理线程销毁线程 事件 事件&#xff08;Event&#xff09;本质上来讲是一种特殊的多播委托&#xff0c;只能从声明它的类中进行调用,基本上说是一个用户操作&…

什么样的人适合学习网络安全?怎么学?_

有很多想要转行网络安全或者选择网络安全专业的人在进行决定之前一定会有的问题&#xff1a;什么样的人适合学习网络安全&#xff1f;我适不适合学习网络安全&#xff1f; 会产生这样的疑惑并不奇怪&#xff0c;毕竟网络安全这个专业在2017年才调整为国家一级学科&#xff0c;…

Pandas操作MultiIndex合并行列的Excel,写入读取以及写入多余行及Index列处理,插入行,修改某个单元格的值,多字段排序

Pandas操作MultiIndex合并行列的excel&#xff0c;写入读取以及写入多余行及Index列处理&#xff0c;多字段排序尽量保持原来的顺序 1. 效果图及问题2. 源码参考 今天是谁写Pandas的 复合索引MultiIndex&#xff0c;写的糊糊涂涂&#xff0c;晕晕乎乎。 是我呀… 记录下&#…

互联网轻量级框架整合之JavaEE基础

不得不解释得几个概念 JavaEE SUN公司提出来的企业版Java开发中间件&#xff0c;主要用于企业级互联网系统的框架搭建&#xff0c;同时因为Java语言优质的平台无关性、可移植性、健壮性、支持多线程和安全性等优势&#xff0c;其迅速成为构建企业互联网平台的主流技术&#x…

【数据结构】树tree

树的遍历 广度遍历Breadth-first traversal Breadth-first traversal is the traversal strategy used in the binary tree.Breadth first traversal, also known as level order traversal is the traversal strategy used in a binary tree. It involves visiting all the …

郭林保大夫——帕金森病明明很早就诊疗了,还是见不到好效果?

郭林保大夫&#xff1a;帕金森是一种常见的神经系统退行性疾病&#xff0c;如果不及时治疗&#xff0c;病情会逐渐加重&#xff0c;导致患者的生活质量严重下降。可能会出现肌肉僵硬、震颤、运动障碍等症状&#xff0c;使患者行动不便&#xff0c;甚至丧失自理能力。此外&#…

实时语音识别(Python+HTML实战)

项目下载地址&#xff1a;FunASR 1 安装库文件 项目提示所需要下载的库文件&#xff1a;pip install -U funasr 和 pip install modelscope 运行过程中&#xff0c;我发现还需要下载以下库文件才能正常运行&#xff1a; 下载&#xff1a;pip install websockets&#xff0c;pi…

ComfyUI SDWebUI升级pytorch随记

目前使用的版本是去年10月的1.6版本&#xff0c;有点老。希望支持新的特性&#xff0c;于是乎开始作死。从升级torch开始。先看看已有的版本&#xff1a; (venv) rootubuntu-sd-server:~# pip show torch Name: torch Version: 2.0.1 Summary: Tensors and Dynamic neural net…

【Spring源码】WebSocket做推送动作的底层实例

一、前瞻 Ok&#xff0c;开始我们今天的对Spring的【模块阅读】。 那就挑Web里的WebSocket模块&#xff0c;先思考下本次阅读的阅读线索&#xff1a; WebSocket在Spring里起到什么作用这个模块采用了什么设计模式我们都知道WebSocket可以主动推送消息给用户&#xff0c;那做推…

点点数据K参数加密逆向分析(RPC方案跟加密算法还原)

文章目录 1. 写在前面2. 接口分析3. 断点分析4. RPC调用5. 算法还原 【&#x1f3e0;作者主页】&#xff1a;吴秋霖 【&#x1f4bc;作者介绍】&#xff1a;擅长爬虫与JS加密逆向分析&#xff01;Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长…

新数字时代的启示:揭开Web3的秘密之路

在当今数字时代&#xff0c;随着区块链技术的不断发展&#xff0c;Web3作为下一代互联网的概念正逐渐引起人们的关注和探索。本文将深入探讨新数字时代的启示&#xff0c;揭开Web3的神秘之路&#xff0c;并探讨其在未来的发展前景。 1. Web3的定义与特点 Web3是对互联网未来发…

金蝶云星空和旺店通·企业奇门接口打通对接实战

金蝶云星空和旺店通企业奇门接口打通对接实战 对接源平台:金蝶云星空 金蝶K/3Cloud&#xff08;金蝶云星空&#xff09;是移动互联网时代的新型ERP&#xff0c;是基于WEB2.0与云技术的新时代企业管理服务平台。金蝶K/3Cloud围绕着“生态、人人、体验”&#xff0c;旨在帮助企业…

机器学习——LightGBM算法

机器学习——LightGBM算法 摘要&#xff1a; LightGBM是一种高效的梯度提升框架&#xff0c;它在处理大规模数据时表现出色&#xff0c;并且具有较快的训练速度和较低的内存消耗。本文将介绍LightGBM算法的原理、特点以及与传统GBDT算法的区别&#xff0c;并使用Python对其进行…