【C++】C++的多态

news/2024/10/4 12:01:33/文章来源:https://blog.csdn.net/2301_81577798/article/details/142315036

目录

多态的使用

多态的概念

多态的定义和实现

虚函数 

构成多态的条件

特殊情况:协变

析构函数的重写

 怎么实现

 为什么实现 

override和final关键字

 override

 final 

重载/重写/隐藏的对比

纯虚函数和抽象类 

 纯虚函数

 抽象类

多态的实现 

 虚函数表指针

 多态的实现

 实现方式

 动态绑定与静态绑定

 虚函数表的存储地址


多态的使用

多态的概念

         多态从字面意思来看就是多种状态,比如说我们前面使用的函数模版swap,重载函数就是多态的一种体现只不过是在编译时就决定的是静态的,我们今天要了解的是多态的动态形式,比如我们在12306购买车票时,作为成人是原价票,学生可以享受优惠,军人可以买到提前票!

多态的定义和实现 

虚函数 

         在前面继承方面我们可能了解到了虚继承,那么什么是虚函数呢?虚函数和虚继承一样需要使用到virtual关键字,虚函数指的是被virtual修饰的成员函数,并且虚函数的出现是为了多态!

         虚函数在类域内外声明和实现时,只需要在类域声明处加上即可!

注意: 静态变量static和virtual不可以同时修饰成员函数

class Base
{
public:// 虚函数virtual void Func(){//...}
};

构成多态的条件

  •  需要存在继承关系,如基类和派生类
  •  被调用的成员函数需要是虚函数,并且构成重写/覆盖关系
  •  必须是基类的指针或者引用指向派生类对象进行调用函数

就拿我们12306买票来收,Person类和Student类,这两者是继承关系,我们需要他们的买票函数构成重写关系,重写关系就是在原本是隐藏关系的基础上给成员函数加上virtual关键字! 

// 买票举例
class Person
{
public:virtual void Buy_Ticket(){cout << "买票-原价" << endl;}
};class Student : public Person
{
public:virtual void Buy_Ticket(){cout << "买票-打折" << endl;}
};

注意:在重写基类虚函数时,派⽣类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派⽣类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使用!

// 买票举例
class Person
{
public:virtual void Buy_Ticket(){cout << "买票-原价" << endl;}
};class Student : public Person
{
public:void Buy_Ticket(){cout << "买票-打折" << endl;}
};

         那么最后我们需要满足最后的第三点,使用基类的指针和引用指向派生类对象,由于继承的规定,只有基类的指针和引用可以指向派生类对象,基类的对象可以赋值派生类对象(切片的原因),为了实现多态效果我们必须使用基类的指针或者引用指向派生类对象进行调用函数!

// 买票举例
//写法一:
class Person
{
public:virtual void Buy_Ticket(){cout << "买票-原价" << endl;}void Test(){Buy_Ticket();}
};class Student : public Person
{
public:virtual void Buy_Ticket(){cout << "买票-打折" << endl;}
};int main()
{Person* p = new Student;p->Test();delete p;return 0;
}

         这段代码由于继承关系,Student继承了Person的两个函数,其中Buy_Ticket()函数构成多态被重写了,但是Test()并没有,该函数在调用时this指针是Person*的类型,我们传入的对象是Student刚好构成多态的条件!

// 写法二:
class Person
{
public:virtual void Buy_Ticket(){cout << "买票-原价" << endl;}
};class Student : public Person
{
public:virtual void Buy_Ticket(){cout << "买票-打折" << endl;}
};void Test(Person* p)
{p->Buy_Ticket();
}int main()
{Person* p = new Student;Test(p);delete p;return 0;
}

        第二种写法同样构成的多态的第三个条件,但是我们需要注意只有在多态的情况下,Test函数才会去掉用Student:: Buy_Ticket(),如果没有构成多态,那么函数Test函数调用的内容由参数类型决定!也就是Person类型。

说明:要实现多态效果,第⼀必须是基类的指针或引⽤,因为只有基类的指针或引⽤才能既指向派⽣类对象;第⼆派⽣类必须对基类的虚函数重写/覆盖,重写或者覆盖了,派⽣类才能有不同的函数,多态的不同形态效果才能达到。 

特殊情况:协变

         派⽣类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引⽤,派⽣类虚函数返回派⽣类对象的指针或者引⽤时,称为协变。

 

析构函数的重写

 怎么实现

         基类的析构函数为虚函数,此时派⽣类析构函数只要定义,⽆论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派⽣类析构函数名字不同看起来不符合重写的规则,实际上编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统⼀处理成destructor,所以基类的析构函数加了vialtual修饰,派⽣类的析构函数就构成重写。

 为什么实现 

 为什么基类中的析构函数建议设计成虚函数

防止内存泄漏:

如果不设计成虚函数 

class A
{
public:~A(){cout << "~A()" << endl;}
};class B : public A
{
public:~B(){cout << "~B()->delete" << _p << endl;}
protected:int* _p = new int[10];
};int main()
{A* p1 = new A;A* p2 = new B;delete p1;delete p2;return 0;
}

我们可以看到并没有释放B类中申请的内存! 

class A
{
public:virtual ~A(){cout << "~A()" << endl;}
};class B : public A
{
public:virtual ~B(){cout << "~B()->delete" << _p << endl;}
protected:int* _p = new int[10];
};int main()
{A* p1 = new A;A* p2 = new B;delete p1;delete p2;return 0;
}

override和final关键字

 override
  •  这个关键字和assert的作用差不多,但是override是在编译时检查,assert是在运行时检查
  •  override可以检查用户是否重写成功虚函数,如果重写失败,编译时就会报错 
 final 
  •  我们不希望派生类去重写基类的某个虚函数时,可以加上这个关键字,这样就无法重写了

 

重载/重写/隐藏的对比

 

纯虚函数和抽象类 

 纯虚函数
  •  在虚函数的后⾯写上=0,则这个函数为纯虚函数
  • 纯虚函数只需要声明即可,不用定义。注意:纯虚函数是可以定义的,只是没有必要 
// 纯虚函数和抽象类
class Pumping_paper
{
public:virtual void using_feel() = 0;void Test(){using_feel();}
};class ManHua : public Pumping_paper
{
public:void using_feel() {cout << "蓬松" << endl;}
};class QingFeng : public Pumping_paper
{
public:void using_feel() {cout << "柔软" << endl;}
};int main()
{Pumping_paper* p1 = new ManHua;Pumping_paper* p2 = new QingFeng;p1->Test();p2->Test();delete p1;delete p2;return 0;
}

 抽象类
  •  只要类中存在纯虚函数都是抽象类
  • 抽象类不能实例化出对象,如果派⽣类继承后不重写纯虚函数,那么派⽣类也是抽象类。纯虚函数某种程度上强制了派⽣类重写虚函数,因为不重写实例化不出对象。 

多态的实现 

 虚函数表指针

  • 虚函数表是一个数组

  • 这是数组存放的是指针

  • 指针是函数指针

  • 总结:存放虚函数指针的数组

/*下⾯编译为32位程序的运⾏结果是什么()
A.编译报错  B.运⾏报错  C.8  D.12*/
class A
{
public:virtual void Func(){//...}
protected:int _a = 1;char _b;
};int main()
{A a1;cout << sizeof(a1) << endl;return 0;
}

 

 

只要class存在虚函数,这些函数的指针都会存放在虚函数表中 

  • 相同类生成的多个对象共用同一种虚函数表 

 多态的实现

 实现方式
class Person
{
public:virtual void Print(){cout << "全价" << endl;}virtual void Func(){//...}void Test(){Print();}protected:string _name = "欧阳";
};class Student : public Person
{
public:virtual void Print(){cout << "打折" << endl;}virtual void Func_S(){//...}
protected:int _id = 1;
};class Child : public Person
{
public:virtual void Print(){cout << "免费" << endl;}
protected:int _age = 6;
};int main()
{Person* p1 = new Person;Person* p2 = new Student;Person* p3= new Child;p1->Test();p2->Test();p3->Test();delete p1;delete p2;return 0;
}

        类中虚函数的指针都会被存放在虚函数表中,派生类会额外开辟一块空间拷贝基类的虚函数表,然后将可以重写的虚函数地址更换为派生类的虚函数 

 动态绑定与静态绑定
  •          对不满⾜多态条件(指针或者引⽤+调⽤虚函数)的函数调⽤是在编译时绑定,也就是编译时确定调⽤函数的地址,叫做静态绑定。
  •         满⾜多态条件的函数调⽤是在运⾏时绑定,也就是在运⾏时到指向对象的虚函数表中找到调⽤函数的地址,也就做动态绑定。

静态绑定 

 动态绑定 

 

 虚函数表的存储地址

虚函数和普通函数一样存放在栈区域 

class Base {
public:virtual void func1() { cout << "Base::func1" << endl; }virtual void func2() { cout << "Base::func2" << endl; }void func5() { cout << "Base::func5" << endl; }
protected:int a = 1;
};class Derive : public Base
{
public :// 重写基类的func1virtual void func1() { cout << "Derive::func1" << endl; }virtual void func3() { cout << "Derive::func1" << endl; }void func4() { cout << "Derive::func4" << endl; }
protected:int b = 2;
};int main()
{int i = 0;static int j = 1;int* p1 = new int;const char* p2 = "xxxxxxxx";printf("栈:%p\n", &i);printf("静态区:%p\n", &j);printf("堆:%p\n", p1);printf("常量区:%p\n", p2);Base b;Derive d;Base* p3 = &b;Derive* p4 = &d;printf("Person虚表地址:%p\n", *(int*)p3);printf("Student虚表地址:%p\n", *(int*)p4);printf("虚函数地址:%p\n", &Base::func1);printf("普通函数地址:%p\n", &Base::func5);return 0;
}

可以看出虚函数表在vs2022是被编译器存放在常量区中 !

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

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

相关文章

【人工智能】OpenAI发布GPT-o1模型:推理能力的革命性突破,这将再次刷新编程领域的格局!

在人工智能领域&#xff0c;推理能力的提升一直是研究者们追求的目标。就在两天前&#xff0c;OpenAI正式发布了其首款具有推理能力的大语言模型——o1。这款模型的推出&#xff0c;不仅标志着AI技术的又一次飞跃&#xff0c;也为开发者和用户提供了全新的工具来解决复杂问题。…

我对 monorepo 的一些思考

我对 monorepo 的一些思考 我对 monorepo 的一些思考 前言它的由来技术选型 管理工具语言与打包调试工具测试框架代码规范与质量控制本地引用与发包替换发包流程Github 相关配置部署 使用手册 功能特性总结如何使用&#xff1f;清除默认的包(可选)模板包介绍 packagesapps 更新…

07_Python数据类型_集合

Python的基础数据类型 数值类型&#xff1a;整数、浮点数、复数、布尔字符串容器类型&#xff1a;列表、元祖、字典、集合 集合 集合&#xff08;set&#xff09;是Python中一个非常强大的数据类型&#xff0c;它存储的是一组无序且不重复的元素&#xff0c;集合中的元素必须…

算法总结:快速排序

快速排序 前言 快速排序算法是托尼霍尔&#xff08;Tony Hoare&#xff09;在1962年提出来的&#xff0c;他是快速排序之父&#xff0c;是1980年图灵奖得主。本篇文章将介绍通过递归的不同写法的方法和非递归的方法来实现简单的快速排序。 一、快速排序的基本思想 快速排序是一…

Django日志

【图书介绍】《Django 5企业级Web应用开发实战&#xff08;视频教学版&#xff09;》_django 5企业级web应用开发实战(视频教学版)-CSDN博客 《Django 5企业级Web应用开发实战&#xff08;视频教学版&#xff09;》(王金柱)【摘要 书评 试读】- 京东图书 (jd.com) Django 5框…

2024最新版MySQL详细学习教程

MySQL数据库提供了很多函数包括&#xff1a; 数学函数&#xff1b;字符串函数&#xff1b;日期和时间函数&#xff1b;条件判断函数&#xff1b;系统信息函数&#xff1b;加密函数&#xff1b;格式化函数&#xff1b; 一、数学函数 数学函数主要用于处理数字&#xff0c;包括…

【算法】滑动窗口—最小覆盖子串

题目 ”最小覆盖子串“问题&#xff0c;难度为Hard&#xff0c;题目如下&#xff1a; 给你两个字符串 S 和 T&#xff0c;请你在 S 中找到包含 T 中全部字母的最短子串。如果 S 中没有这样一个子串&#xff0c;则算法返回空串&#xff0c;如果存在这样一个子串&#xff0c;则可…

【MySQL】Windows下重启MySQL服务时,报错:服务名无效

1、问题描述 在终端中&#xff0c;停止、启动MySQL服务时报错&#xff1a;服务名无效 2、原因分析 1&#xff09;权限不够 如果是权限不够&#xff0c;会提示&#xff1a;系统错误5&#xff0c;拒绝访问。 2&#xff09;服务名错误 如果是服务名错误&#xff0c;会提示“…

09年408考研真题解析-计算机网络

[题34]在无噪声情况下&#xff0c;若某通信链路的带宽为3kHz&#xff0c;采用4个相位&#xff0c;每个相位具有4种振幅的QAM调制技术,则该通信链路的最大数据传输速率是&#xff08;B&#xff09; A.12 kbps B.24 kbps C.48 kbps D.96 kbps 解析&#xff…

css百分比布局中height:100%不起作用

百分比布局时&#xff0c;我们有时候会遇到给高度 height 设置百分比后无效的情况&#xff0c;而宽度设置百分比却是正常的。 当为一个元素的高度设定为百分比高度时&#xff0c;是相对于父元素的高度来计算的。当没有给父元素设置高度&#xff08;height&#xff09;时或设置…

YOLOv9改进策略【卷积层】| GnConv:一种通过门控卷积和递归设计来实现高效、可扩展、平移等变的高阶空间交互操作

一、本文介绍 本文记录的是利用GnConv优化YOLOv9的目标检测方法研究。YOLOv9在进行目标检测时&#xff0c;需要对不同层次的特征进行融合。GnConv可以考虑更高阶的空间交互&#xff0c;能够更好地捕捉特征之间的复杂关系&#xff0c;从而增强特征融合的效果&#xff0c;提高模…

自修C++PrimerPlus--类型转换、右值引用、引用中的类对象

目录 1.类型转换介绍 2.关闭vs2022的报警系统 3.string里面的I/O 4.引用和左值引用 4.1左值和右值的说明 4.2具体的代码演示 4.3字符和字符串的const区分 4.4右值引用的示例介绍 5.将引用应用于类对象 6.函数和C风格字符串 6.1两者的区别 6.2演示案例 1.类型转换介…

Ubuntu 20.04 部署 NET8 Web - Systemd 的方式 达到外网访问的目的

1.Ubuntu服务器环境安装 1.1 增加微软包安装源 wget https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb sudo dpkg -i packages-microsoft-prod.deb1.2 Install the .NET SDK # 更新本地软件包列表。原理&am…

k8s的环境配置

一、前期系统环境准备 准备3台主机&#xff1a;硬盘50G cpu2个 内存2G 1、3台主机同时配置 1&#xff09;关闭防火墙与selinux、NetworkManager [rootk8s-master ~]# systemctl stop firewalld[rootk8s-master ~]# systemctl disable firewalldRemoved symlink /etc/systemd/…

流动性质押协议 Drop:DeFi 新一轮革新

近年来&#xff0c;去中心化金融&#xff08;DeFi&#xff09;领域经历了迅猛的增长和创新&#xff0c;而其中一项重要的发展便是流动性质押协议的兴起。在传统的区块链网络中&#xff0c;用户为了参与网络的验证过程和维护网络安全&#xff0c;通常需要将加密资产锁定在区块链…

极验3代前两个参数w逆向分析

声明: 该文章为学习使用,严禁用于商业用途和非法用途,违者后果自负,由此产生的一切后果均与作者无关。 本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请联系作者立即删除! 前言 这次会简单的讲解…

关于STM32项目面试题01:电源篇

博客的风格是&#xff1a;答案一定不能在问题的后面&#xff0c;要自己想、自己背&#xff1b;回答都是最精简、最精简、最精简&#xff0c;可能就几个字&#xff0c;你要自己自信的展开。 面试官01&#xff1a;说说你知道的开关电源的拓扑结构&#xff1f; 面试官02&#xff1…

带你如何使用CICD持续集成与持续交付

目录 一、CICD是什么 1.1 持续集成&#xff08;Continuous Integration&#xff09; 1.2 持续部署&#xff08;Continuous Deployment&#xff09; 1.3 持续交付&#xff08;Continuous Delivery&#xff09; 二、git工具使用 2.1 git简介 2.2 git的工作流程 2.3 部署g…

代码随想录算法训练营第57天|卡码网 53. 寻宝 prim算法精讲和kruskal算法精讲

1. prim算法精讲 题目链接&#xff1a;https://kamacoder.com/problempage.php?pid1053 文章链接&#xff1a;https://www.programmercarl.com/kamacoder/0053.寻宝-prim.html prim算法 是从节点的角度 采用贪心的策略 每次寻找距离 最小生成树最近的节点 并加入到最小生成树中…

大模型笔记03--快速体验dify

大模型笔记03--快速体验dify 介绍部署&测试部署 dify测试dify对接本地ollama大模型对接阿里云千问大模型在个人网站中嵌入dify智能客服 注意事项说明 介绍 Dify 是一款开源的大语言模型(LLM) 应用开发平台。它融合了后端即服务&#xff08;Backend as Service&#xff09;…