多态(C++)

news/2024/5/4 2:59:04/文章来源:https://blog.csdn.net/qq_68006585/article/details/129936667

多态

  • 多态的概念
    • 概念
  • 多态的定义及实现
    • 多态的构成条件
    • 虚函数
    • 虚函数的重写
      • 虚函数重写的两个例外
    • C++11override和final
    • 重载,覆盖,隐藏的对比
  • 抽象类
    • 概念
    • 接口继承和实现继承
  • 多态的原理
    • 虚函数表
    • 多态的原理
    • 动态绑定与静态绑定
  • 单继承和多继承关系中的虚函数表
    • 单继承中的虚函数表
    • 多继承中的虚函数表

多态的概念

概念

多态即多种形态,当不同对象去执行同一个行为时,会产生不同的状态

多态的定义及实现

多态的构成条件

  1. 必须通过基类的指针/引用去调用虚函数( virtual所修饰)
  2. 被调用的函数必须是虚函数,并且派生类必须对基类的虚函数进行重写

在这里插入图片描述

class Person
{
public:virtual void Buyticket(){cout << "Person:买票-全价" << endl;}
};class Student:public Person
{
public:virtual void Buyticket(){cout << "Student:买票-半价" << endl;}
};//多态调用
void Display(Person& p)
{p.Buyticket();
}int main()
{Person pn;Display(pn);Student st;Display(st);return 0;
}

在这里插入图片描述

普通调用:和调用对象的类型有关
多态调用:和指针/引用指向的对象有关

class Person
{
public:virtual void Buyticket(){cout << "Person:买票-全价" << endl;}
};class Student:public Person
{
public:virtual void Buyticket(){cout << "Student:买票-半价" << endl;}
};//普通调用
void Display(Person p)
{p.Buyticket();
}int main()
{Person pn;Display(pn);Student st;Display(st);return 0;
}

在这里插入图片描述

虚函数

虚函数:被 virtual修饰的类成员函数称作虚函数

class Person
{
public:virtual void Buyticket(){cout << "Person:买票-全价" << endl;}
};

虚函数的重写

虚函数的重写:派生类中有一个和基类完全相同的虚函数,称派生类的虚函数重写了基类的虚函数

虚函数重写的两个例外

  1. 协变(基类与派生类虚函数返回值类型不同)
    派生类重写基类虚函数时,与基类虚函数返回值类型不同。也就是基类虚函数返回基类对象的指针/引用,派生类虚函数返回派生类对象的指针/引用
class Person
{
public:~Person(){cout << "~Person()" << endl;delete[] _p;}
protected:int* _p = new int[10];
};class Student :public Person
{
public:~Student(){cout << "~Student()" << endl;delete[] _s;}
protected:int* _s = new int[10];
};int main()
{Person pn;Student st;return 0;
}

在这里插入图片描述

  1. 析构函数的重写(基类与派生类析构函数名不同)
    先观察如果析构函数不设置为虚函数
class Person
{
public:~Person(){cout << "~Person()" <<endl;delete[] _p;}
protected:int* _p = new int[10];
};class Student :public Person
{
public:~Student(){cout << "~Student()" << endl;delete[] _s;}
protected:int* _s = new int[20];
};int main()
{Person pn;Student st;return 0;
}

在这里插入图片描述

运行起来似乎没有什么问题,做如下修改,结果又是如何

int main()
{Person* ptr1 = new Person;Person* ptr2 = new Student;delete ptr1;delete ptr2;return 0;
}

在这里插入图片描述

由运行结果可知,发生了内存泄漏,Student的资源并没有释放;delete的行为是:使用指针调用析构函数,由于是普通调用,函数与调用对象的类型有关,所以造成了内存泄漏;如果将函数设置为虚函数结构会怎么样呢?

class Person
{
public:virtual ~Person(){cout << "~Person()" << endl;delete[] _p;}
protected:int* _p = new int[10];
};class Student :public Person
{
public:virtual ~Student(){cout << "~Student()" << endl;delete[] _s;}
protected:int* _s = new int[20];
};

在这里插入图片描述

内存泄漏的问题完美地解决

虽然函数名不同,这里其实是构成了虚函数的重写,编译器对析构函数的名称做了处理,编译后的析构函数的名称统一处理成 destructor

  1. 子类虚函数可以不加 virtual修饰(不推荐)

C++11override和final

  1. final:修饰虚函数,表示该虚函数不能被重写
  2. override:检查派生类虚函数是否重写了某类的某个虚函数,如果没有重写编译报错

重载,覆盖,隐藏的对比

在这里插入图片描述

抽象类

概念

在虚函数的后面加上=0,称这个函数为纯虚函数,包含纯虚函数的类称作抽象类,抽象类不能实例化对象;派生类继承后也不能实例化对象,只有重写纯虚函数,派生类才能实例化对象;纯虚函数规定了派生类必须重写

class Car
{
public:virtual void Drive()=0;
};int main()
{Car c;return 0;
}

在这里插入图片描述

纯虚函数不可以实例化

class Car
{
public:virtual void Drive() = 0;
};class NIO : public Car
{
public:virtual void Drive(){cout << "安全驾驶" << endl;}
};int main()
{NIO et7;et7.Drive();return 0;
}

在这里插入图片描述

接口继承和实现继承

普通函数的继承是是实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现;虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,构造多态,继承的是接口

多态的原理

虚函数表

class Base
{
public:virtual void Test(){cout << "Test()" << endl;}
private:int _a = 0;
};int main()
{Base b;cout << sizeof(b) << endl;return 0;
}

按照以往的方式计算 Base类的大小:类中成员变量只有 int类型大小四个字节,成员函数 Test不占空间,所以类的大小应该是四个字节

运行结果如下

在这里插入图片描述

运行结果和预算的结果不一样,这是怎么回事呢?难道是成员函数也要计算大小吗?接下来,通过监视一探究竟

在这里插入图片描述

原来对象类中除了成员变量之外,还有_vfptr,也就是接下来要学习的虚函数表指针,想要学习指针,就先来了解虚函数表

虚函数表:用来存放虚函数,就如上面[0]中存放的就是Test的地址;本质就是函数指针数组

在这里插入图片描述

class Car
{
public:virtual void Test1(){cout << "Car:Test1()" << endl;}virtual void Test2(){cout << "Car:Test2()" << endl;}
private:int _a = 0;
};class NIO : public Car
{
public:virtual void Test1(){cout << "NIO:Test1()" << endl;}private:int _b = 0;
};int main()
{Car c;NIO et7;return 0;
}

在这里插入图片描述

完成重写的虚函数,表函数表对应位置覆盖成重写的虚函数

多态的原理

int main()
{Car c;NIO et7;//多态调用Car* ptr = &c;ptr->Test1();ptr = &et7;ptr->Test1();//普通调用ptr = &c;ptr->Test2();ptr = &et7;ptr->Test2();return 0;
}

运行结果如下

在这里插入图片描述

普通调用由调用对象类型决定:Test2()函数不是虚函数,调用类型是Car;多态调用由指针/引用指向的对象决定:Test1()函数是虚函数,两次调用指向的对象都不同

再通过汇编进行观察

在这里插入图片描述

普通调用在编译时就确定好的(静态);多态调用在编译时是不确定的(动态)运行之后在,根据调用对象指向的类型,在表函数表中找到对应的函数进行调用

在这里插入图片描述

动态绑定与静态绑定

  1. 静态绑定,在程序编译期间就已经确定程序的行为,也称静态多态
  2. 动态绑定,在程序运行期间,根据具体的类型确定程序具体的行为,调用具体的函数,也称动态多态

单继承和多继承关系中的虚函数表

单继承中的虚函数表

观察下列代码

class Car
{
public:virtual void test1(){cout << "Car test1()" << endl;}virtual void test2(){cout << "Car test2()" << endl;}private:int _a;
};class NIO:public Car
{
public:virtual void test1(){cout << "NIO test1()" << endl;}virtual void test3(){cout << "NIO test3()" << endl;}private:int _b;
};int main()
{Car c;NIO et7;return 0;
}

在这里插入图片描述

通过监视窗口能够发现:在派生类对象et7中重写了test1,继承了test2,但是本身的test3却没有,在内存窗口看到一个未知的地址,可以猜测是test3函数,现在就是想办法将其打印出来进行验证

上面了解到虚函数表本质是函数指针数组,接下来通过函数指针数组将虚函数表打印出来

typedef void(*_vfptr)();void Print_vfptr(_vfptr vft[])
{for (int i = 0; vft[i] != nullptr; i++){printf("[%d]:%p->", i, vft[i]);vft[i]();}cout << endl;
}

通过将对象进行取址再强转 int*获取前四个字节,在类型转换 _vfptr*传递给函数进行打印

	Print_vfptr((_vfptr*)*(int*)&c);Print_vfptr((_vfptr*)*(int*)&et7);

完整代码如下

class Car
{
public:virtual void test1(){cout << "Car test1()" << endl;}virtual void test2(){cout << "Car test2()" << endl;}private:int _a;
};class NIO:public Car
{
public:virtual void test1(){cout << "NIO test1()" << endl;}virtual void test3(){cout << "NIO test3()" << endl;}private:int _b;
};typedef void(*_vfptr)();void Print_vfptr(_vfptr vft[])
{for (int i = 0; vft[i] != nullptr; i++){printf("[%d]:%p->", i, vft[i]);vft[i]();}cout << endl;
}int main()
{Car c;Print_vfptr((_vfptr*)*(int*)&c);NIO et7;Print_vfptr((_vfptr*)*(int*)&et7);return 0;
}

在这里插入图片描述

打印结果和预期一致

在这里插入图片描述

多继承中的虚函数表

class NIO
{
public:virtual void test1(){cout << "NIO test1()" << endl;}virtual void test2(){cout << "NIO test2()" << endl;}private:int _a;
};class XPENG
{
public:virtual void test1(){cout << "XPENG test1()" << endl;}virtual void test2(){cout << "XPENG test2()" << endl;}private:int _b;
};class NEA :public NIO, public XPENG
{
public:virtual void test1(){cout << "NEA test1()" << endl;}virtual void test3(){cout << "NEA test3()" << endl;}private:int _c;
};int main()
{NIO et7;XPENG p7;NEA car;return 0;
}

在这里插入图片描述

通过监视会发现与上面类似的疑问,派生类 NEA对象本身的虚函数存放在第一个基类 NIO中还是第二个基类 XPENG中呢?

接下来再次通过函数指针数组进行验证

typedef void(*_vfptr)();void Print_vfptr(_vfptr vft[])
{for (int i = 0; vft[i] != nullptr; i++){printf("[%d]:%p->", i, vft[i]);vft[i]();}cout << endl;
}class NIO
{
public:virtual void test1(){cout << "NIO test1()" << endl;}virtual void test2(){cout << "NIO test2()" << endl;}private:int _a;
};class XPENG
{
public:virtual void test1(){cout << "XPENG test1()" << endl;}virtual void test2(){cout << "XPENG test2()" << endl;}private:int _b;
};class NEA :public NIO, public XPENG
{
public:virtual void test1(){cout << "NEA test1()" << endl;}virtual void test3(){cout << "NEA test3()" << endl;}private:int _c;
};int main()
{NIO et7;Print_vfptr((_vfptr*)*(int*)&et7);XPENG p7;Print_vfptr((_vfptr*)*(int*)&p7);NEA car;//NIO虚函数表Print_vfptr((_vfptr*)*(int*)&car);XPENG* ptr = &p7;//XPENG虚函数表Print_vfptr((_vfptr*)*(int*)ptr);return 0;
}

在这里插入图片描述

打印结果显示:派生类本身的虚函数是存放在第一个基类的虚函数表中的

在这里插入图片描述

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

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

相关文章

QT完善登录界面Ⅱ

功能添加&#xff1a; 1.弹窗提示 2.页面跳转 信号的发送&#xff0c;槽函数执行 form.hpublic slots:void mySlot(); //槽函数widget.h signals:void mySignal(QString e); //自定义属于自己的信号函数//widget.cpp #include "widget.h" #include "ui_widge…

Java初阶 ( String 类)

文章目录一、String 类的基础概念1.1 Java 中的字符串1.2 字符串的构造二、String 类的进阶概念2.1 求字符串的长度2.2 isEmpty()2.3 字符串的比较2.4 字符串的查找2.5 字符串的转换2.6 字符串的替换2.6 字符串的拆分2.7 字符串的截取2.8 去掉字符串的左右空白字符2.9 StringBu…

Leetcode.226 翻转二叉树

题目链接 Leetcode.226 翻转二叉树 easy 题目描述 给你一棵二叉树的根节点 root&#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1] 示例 2&#xff1a; 输入&#xff1a;r…

C++对象模型与this指针

一、成员变量与成员函数分开存储 1、在C中&#xff0c;类内的成员变量和成员函数分开存储 首先&#xff0c;对于一个空对象&#xff0c;占用内存空间为1 class person {};void test01() {person p;cout << sizeof(p) << endl; } 因为C编译器给每个空对象分配1个字…

【juc】wait和notify原理

目录一、monitor锁结构图二、说明一、monitor锁结构图 二、说明 1.线程1一开始持有对象A的monitor锁&#xff0c;即monitor中的owner指向线程1 2.线程1在执行的过程中发现条件a不满足执行不下去了&#xff0c;此时线程1可以调用wait方法&#xff0c;那么线程1就进入waitset进行…

【RabbitMQ高级篇】消息可靠性问题(1)

目录 1.消息可靠性 1.1.生产者消息确认 1.1.1.修改配置 1.1.2.定义Return回调 1.1.3.定义ConfirmCallback 1.2.消息持久化 1.2.1.交换机持久化 1.2.2.队列持久化 1.2.3.消息持久化 1.3.消费者消息确认 1.3.1.演示none模式 1.3.2.演示auto模式 1.4.消费失败重试机制…

.net C#反编译及脱壳常用工具--小结

1、Reflector --微软自家工具--推荐 Reflector是最为流行的.Net反编译工具。Reflector是由微软员工Lutz Roeder编写的免费程序。Reflector的出现使NET程序员眼前豁然开朗&#xff0c;因为这个免费工具可以将NET程序集中的中间语言反编译成C#或者Visual Basic代码。除了能将IL转…

五、页面切割技术,实现工作台

页面切割技术 1.<frameset>和<frame> <frameset>:用来切割页面 <frameset cols"20%,60%,20%"> 竖着把窗口切三部分 <frameset rows"20%,60%,20%"> 横着把窗口切三部分 <frame>&#xff1a;用来显示页面 <frame …

三星公司因ChatGPT造成数据泄露?

作者丨黑蛋 ChatGPT大家最近应该都听过很多&#xff0c;关于各种ChatGPT消息铺天盖地&#xff0c;将会取代大部分人工&#xff0c;ChatGPT代替创作&#xff0c;绘画&#xff0c;很多公司因此裁员等消息多不胜数&#xff0c;甚至短短几个月&#xff0c;ChatGPT升级版ChatGPT4就…

无需服务器免费上线你的静态网页

无需服务器免费上线你的静态网页:https://s.qiniu.com/bmaYJf

Keil 5 安装教程及简单使用【嵌入式系统】

Keil 5 安装教程【嵌入式系统】前言推荐说明keil5安装教程第一阶段&#xff1a;安装mdk第二阶段&#xff1a;激活mdk第三阶段&#xff1a;安装STM32芯片包第四阶段&#xff1a;安装C51单片机第五阶段&#xff1a;激活C51单片机keil 5的简单使用1建立新工程2创建新文件3.生成HEX…

华硕 ASUS-PRIME-B560M-A Intel Core i5-11400黑苹果efi引导文件

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。&#xff08;下载请直接百度黑果魏叔&#xff09; 硬件型号驱动情况 主板ASUS-PRIME-B560M-A 处理器Intel Core i5-11400已驱动 内存16GB DDR4 3200 Mhz已驱动 硬盘Western Digital Black SN750 500GB已驱动 显卡SAPPH…

社区团购是什么?打破传统消费模式的新选择

社区团购作为一种新兴的消费模式&#xff0c;已经成为了越来越多人的选择。在社区团购中&#xff0c;商家可以通过团购的方式向消费者提供优惠的价格和服务&#xff0c;同时也可以借助社区团购来扩大销售渠道和提高品牌知名度。本文将以一家小型便利店的社区团购为例&#xff0…

艾瑞巴蒂看过来!OSSChat 上线:融合 CVP,试用通道已开放

还在纠结于反复查找开源项目的技术文档&#xff1f; 团队常因频繁搜索开源项目主页导致效率低下&#xff1f; 每天都要问一遍【开源项目中那些“小白问题”究竟有没有更快的解决方法&#xff1f;】 对此&#xff0c;只想对你说&#xff1a;赶紧试试 OSSChat&#xff01;赶紧试…

灵动MM32 MindSPIN系列MCU —— 无刷电机驱动的得力伙伴

无论是在工业应用&#xff0c;还是智能家居和物联网应用上&#xff0c;提高效率和节能减碳一直为其主轴诉求&#xff0c;而有着兼顾于高效与节能特色的直流无刷电机&#xff0c;正是符合此应用的主流。 灵动微电子MindSPIN系列MCU产品就是针对直流无刷电机驱动所量身打造的。由…

Leetcode.112 路径总和

题目链接 Leetcode.112 路径总和 easy 题目描述 给你二叉树的根节点 root和一个表示目标和的整数 targetSum。判断该树中是否存在 根节点到叶子节点 的路径&#xff0c;这条路径上所有节点值相加等于目标和 targetSum。如果存在&#xff0c;返回 true&#xff1b;否则&#xf…

自学编程的5大误区,早知道早避坑,过来人的宝贵经验

前言 有的人自学很快&#xff0c;几乎一个多月就能掌握一门技术&#xff0c;而有的人苦苦坚持&#xff0c;最后还是半途而废&#xff0c;很大的原因就在于在学习的时候掉进了一些误区没能走出来。 今天我们就来讲讲自学编程常见的5大误区&#xff0c;避开这些误区我们定能在自…

美团全国各配送站机房配备深圳钡铼技术工业物联网监测终端S270,实现远程数据监测

美团集团与钡铼技术&#xff0c;日前签约美团旗下全国各配送站机房监测项目。深圳钡铼技术为美团每家配送站机房配备工业物联网数据监测终端S270&#xff0c;接入美团系统&#xff0c;助力美团集团实现物联网升级。实现远程采集仓库机房水浸、温湿度、烟感、停电报警等数据&…

“成年人”的数据库,既要又要也要!

欢迎访问 OceanBase 官网获取更多信息&#xff1a;https://www.oceanbase.com/ 3 月 25 日&#xff0c;第一届 OceanBase 开发者大会在北京举行&#xff0c;《明说三人行》访谈栏目创始人兼主持人卢东明、沃趣科技创始人兼 CEO 陈栋、DBAplus 社群联合创始人杨建荣、PostgreSQL…

强化学习——初探强化学习

本文引自&#xff1a;《 动手学强化学习 》 第 1 章 初探强化学习 1.1 简介 亲爱的读者&#xff0c;欢迎来到强化学习的世界。初探强化学习&#xff0c;你是否充满了好奇和期待呢&#xff1f;我们想说&#xff0c;首先感谢你的选择&#xff0c;学习本书不仅能够帮助你理解强…