C++知识整理day10——多态(多态的定义和实现、虚函数重写/覆盖、override和final关键字、纯虚函数和抽象类、多态的原理)

文章目录

  • 1.多态的概念
  • 2.多态的定义和实现
    • 2.1 多态的构成条件
    • 2.2 多态必须具备的两个条件(很重要)
    • 2.3 虚函数
    • 2.4 虚函数的重写/覆盖
    • 2.5 协议(了解即可)
    • 2.6 析构函数的重写
    • 2.6 override和final关键字
    • 2.7 重载/重写/隐藏的对比
  • 3.纯虚函数和抽象类
  • 4.多态的原理

1.多态的概念

多态(polymorphism)的概念:通俗来说,就是多种形态。多态分为编译时多态(静态多态)和运⾏时多态(动态多态)。
编译时多态(静态多态)主要就是我们前⾯讲的函数重载和函数模板,他们传不同类型的参数就可以调⽤不同的函数,通过参数不同达到多种形态,之所以叫编译时多态,是因为他们实参传给形参的参数匹配是在编译时完成的,我们把编译时⼀般归为静态,运⾏时归为动态。
运⾏时多态,具体点就是去完成某个⾏为(函数),可以传不同的对象就会完成不同的⾏为,就达到多种形态。⽐如买票这个⾏为,当普通⼈买票时,是全价买票;学⽣买票时,是优惠买票(5折或75折);军⼈买票时是优先买票。再⽐如,同样是动物叫的⼀个⾏为(函数),传猫对象过去,就是”(>ω<)喵“,传狗对象过去,就是"汪汪"。

2.多态的定义和实现

2.1 多态的构成条件

多态是⼀个继承关系的下的类对象,去调⽤同⼀函数,产⽣了不同的⾏为。⽐如Student继承了Person。Person对象买票全价,Student对象优惠买票。

2.2 多态必须具备的两个条件(很重要)

  1. 必须是基类的指针或者引⽤调⽤虚函数
  2. 被调⽤的函数必须是虚函数,并且完成了虚函数重写/覆盖。

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

在这里插入图片描述

2.3 虚函数

类成员函数前⾯加virtual修饰,那么这个成员函数被称为虚函数。注意⾮成员函数不能加virtual修饰

示例:

class Person
{
public:virtual void BuyTicket(){cout << "全价-半价车票" << endl;}
};

函数BuyTicket就是一个虚函数。

2.4 虚函数的重写/覆盖

虚函数的重写/覆盖:派⽣类中有⼀个跟基类完全相同的虚函数(即派⽣类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称派⽣类的虚函数重写了基类的虚函数。

注意: 在重写基类虚函数时,派⽣类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派⽣类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使⽤,不过在考试选择题中,经常会故意买这个坑,让你判断是否构成多态。

示例:

class Person
{
public:virtual void BuyTicket(){cout << "全价-半价车票" << endl;}
};class Student : public Person
{virtual void BuyTicket(){cout << "半价车票" << endl;}
};void Func(Person* ptr)//必须是基类的指针或者引用才可以
{ptr->BuyTicket();
}int main()
{Person p1;Student s1;Func(&p1);Func(&s1);return 0;
}

输出结果:
在这里插入图片描述

一道有点恶心的面试题!!!

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;
}

上面程序的输出结果是什么(B)
A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确

【分析】首先,p是基类的指针,再次,调用的是虚函数test,完成多态的条件。而在基类的test函数中,是this(p传给的this)调用了func函数,而this是基类的指针,调用的是虚函数(派生了的虚函数可以省略virtual关键字),这也完成了多态的条件,所以很多人的选择是D(B->0)!!!但是这里又有一个坑,==我们在调用虚函数的时候,函数里面的参数还是用的这个本身类里面虚函数的参数!!!==所以本道题选B

2.5 协议(了解即可)

派⽣类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引⽤,派⽣类虚函数返回派⽣类对象的指针或者引⽤时(必须统一起来),称为协变。 协变的实际意义并不⼤,所以我们了解⼀下即可。

示例:
在这里插入图片描述

2.6 析构函数的重写

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

下⾯的代码我们可以看到,如果~A(),不加virtual,那么delete p2时只调⽤的A的析构函数,没有调⽤
B的析构函数,就会导致内存泄漏问题,因为~B()中在释放资源。

注意: 这个问题⾯试中经常考察,⼤家⼀定要结合类似下⾯的样例才能讲清楚,为什么基类中的析构
函数建议设计为虚函数。

class A
{
public:virtual ~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;//只有派⽣类Student的析构函数重写了Person的析构函数,下⾯的delete对象调⽤析构函数,//才能构成多态,才能保证p1和p2指向的对象正确的调⽤析构函数。delete p1;delete p2;return 0;
}

在这里插入图片描述
【总结】如果基类的析构函数不是虚函数,在通过基类指针删除派生类对象时,只会调用基类的析构函数,这可能导致派生类的资源没有正确释放,造成内存泄漏。

2.6 override和final关键字

从上⾯可以看出,C++对虚函数重写的要求⽐较严格,但是有些情况下由于疏忽,⽐如函数名写错参数写错等导致⽆法构成重写,⽽这种错误在编译期间是不会报出的,只有在程序运⾏时没有得到预期结果才来debug会得不偿失,因此C++11提供了override,可以帮助用户检测是否重写。如果我们不想让派⽣类重写这个虚函数,那么可以⽤final去修饰。(final关键字放在类名之后,表示的是这个类不可以被继承)

示例1:

class Car
{
public:virtual void Drive() {}
};class Benz : public Car
{
public:virtual void Drive() override{cout << "Benz" << endl;}
};

示例2:
在这里插入图片描述

2.7 重载/重写/隐藏的对比

easy!!!
在这里插入图片描述

3.纯虚函数和抽象类

在虚函数的后⾯写上 =0 ,则这个函数为纯虚函数,纯虚函数不需要定义实现(实现没啥意义因为要被派⽣类重写,但是语法上可以实现),只要声明即可。==包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象,如果派⽣类继承后不重写纯虚函数,那么派⽣类也是抽象类。==纯虚函数某种程度上强制了派⽣类重写虚函数,因为不重写实例化不出对象。

示例:

class Car
{
public:virtual void Drive() = 0;//纯虚函数,Car就是个抽象类,不可以实例化
};class Benz : public Car
{
public:virtual void Drive(){cout << "Benz" << endl;}
};class BMW : public Car
{
public:virtual void Drive(){cout << "BMW" << endl;}
};

4.多态的原理

这里简单说明一下。。。

我们先看一道题目:

class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
protected:int _b = 1;char _ch = 'x';
};
int main()
{Base b;cout << sizeof(b) << endl;return 0;
}

上面编译为32位程序的运⾏结果是什么(D)
A. 编译报错 B. 运⾏报错 C. 8 D. 12

【分析】 我们知道类和结构体一样需要满足内存对齐的原则,那么按道理来说,b的大小应该是8!为什么答案是12呢?其实,除了_b和_ch成员,还多⼀个__vfptr(虚函数表指针)放在对象的前⾯(注意有些平台可能会放到对象的最后⾯,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。⼀个含有虚函数的类中都⾄少都有⼀个虚函数表指针,因为⼀个类所有虚函数的地址要被放到这个类对象的虚函数表中,虚函数表也简称虚表。

我们以上面买票的例子说明一下多态的底层原理。

从底层的⻆度Func函数中ptr->BuyTicket(),是如何作为ptr指向Person对象调⽤Person::BuyTicket,ptr指向Student对象调⽤Student::BuyTicket的呢?通过下图我们可以看到,满⾜多态条件后,底层不再是编译时通过调⽤对象确定函数的地址,⽽是运⾏时到指向的对象的虚表中确定对应的虚函数的地址,这样就实现了指针或引⽤指向基类就调⽤基类的虚函数,指向派⽣类就调⽤派⽣类对应的虚函数。第⼀张图,ptr指向的Person对象,调⽤的是Person的虚函数;第⼆张图,ptr指向的Student对象,调⽤的是Student的虚函数。

在这里插入图片描述
我在vs调试中带大家详细的看一下虚函数表指针。

class Person
{
public:virtual void BuyTicket(){cout << "全价-半价车票" << endl;}
};class Student : public Person
{virtual void BuyTicket(){cout << "半价车票" << endl;}
};class Workers : public Person
{virtual void BuyTicket(){cout << "全价车票" << endl;}
};void Func(Person* ptr)//必须是基类的指针或者引用才可以
{ptr->BuyTicket();
}int main()
{Person p1;Student s1;Workers w1;Func(&p1);Func(&s1);Func(&w1);return 0;
}

在这里插入图片描述
总结一下虚函数表指针:

  1. 基类对象的虚函数表中存放基类所有虚函数的地址。同类型的对象共⽤同⼀张虚表,不同类型的对象各⾃有独⽴的虚表,所以基类和派⽣类有各⾃独⽴的虚表
  2. 派⽣类由两部分构成,继承下来的基类和⾃⼰的成员,⼀般情况下,继承下来的基类中有虚函数表指针,⾃⼰就不会再⽣成虚函数表指针。但是要注意的这⾥继承下来的基类部分虚函数表指针和基类对象的虚函数表指针不是同⼀个(因为虚函数的地址是不一样的),就像基类对象的成员和派⽣类对象中的基类对象成员也独⽴的。
  3. 派⽣类中重写的基类的虚函数,派⽣类的虚函数表中对应的虚函数就会被覆盖成派⽣类重写的虚函数地址。
  4. 派⽣类的虚函数表中包含,(1)基类的虚函数地址,(2)派⽣类重写的虚函数地址完成覆盖,派⽣类⾃⼰的虚函数地址三个部分。
  5. 虚函数表本质是⼀个存虚函数指针的指针数组,⼀般情况这个数组最后⾯放了⼀个0x00000000标记。(这个C++并没有进⾏规定,各个编译器⾃⾏定义的,vs系列编译器会再后⾯放个0x00000000标记,g++系列编译不会放)
  6. 虚函数存在哪的?虚函数和普通函数⼀样的,编译好后是⼀段指令,都是存在代码段的,只是虚函数的地址⼜存到了虚表中。
  7. 虚函数表存在哪的?这个问题严格说并没有标准答案C++标准并没有规定,大家可以通过代码
    对⽐验证⼀下。vs下是存在代码段(常量区)

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

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

相关文章

BladeX框架接口请求跨域

前端使用代理请求接口&#xff0c;接口可以正常访问。如果换全路径请求就跨域。 除了后端要配置跨域 还需要修改配置文件对OPTIONS请求的限制

文件操作 -- IO [Java EE 初阶]

目录 文件 1. 认识文件 2. 树型结构组织和目录 3. 文件路径 (Path) 4. 文件系统上存储的文件又可以分为两大类 4.1 文本文件 4.2 二进制文件 文件系统操作 1.Java 中操作文件 2. File 概述 2.1 属性 2.2 构造方法 2.3 方法 2.4 部分举例 文件内容操作 1. 数据流…

菜鸟之路Day19一一多线程(一)

菜鸟之路Day19一一多线程&#xff08;一&#xff09; 作者&#xff1a;blue 时间&#xff1a;2025.2.24 文章目录 菜鸟之路Day19一一多线程&#xff08;一&#xff09;o.概述1.什么是多线程2.并发与并行3.多线程的实现方式3.1继承Thread类的方式进行实现3.2实现Runnable接口的…

《Effective Objective-C》阅读笔记(上)

目录 高质量iOS之熟悉OC 了解OC语言的起源 在类的头文件中尽量少引入其他头文件 多用字面语法&#xff0c;少用与之等价的方法 字面数值 字面量数组 字面量字典 局限性 多用类型常量&#xff0c;少用#define预处理指令 用枚举表示状态、选项、状态码 高质量iOS之对象…

mysql --- 相关基础知识整理

目录 一、基本数据结构1、聚簇索引和非聚簇索引1.1 数据存储方式1.2 查询效率1.3 插入和更新性能1.4 适用场景 2、InnoDB 存储引擎2.1 B树2.2 行格式2.3 缓冲池2.4 日志文件 3、MyISAM存储引擎3.1 表文件结构3.2 B树3.3 数据存储特点 4、InnoDB和MyISAM的区别 二、索引1、索引类…

JSX 实现列表渲染

const list [{ id: 1001, name: Vue },{ id: 1002, name: React },{ id: 1003, name: Angular },{ id: 1004, name: Node }, ] function App() {return (<div className"App">this is App{/* 渲染列表 */}<ul>{list.map(item > <li key{item.id}&…

ue5.2.1 quixel brideg显示asset not available in uAsset format

我从未见过如此傻x的bug&#xff0c;在ue5.2.1上通过内置quixel下载资源显示 asset not available in uAsset format 解决办法&#xff1a;将ue更新到最新版本&#xff0c;通过fab进入商场选择资源后add to my library 点击view in launcher打开epic launcher&#xff0c;就可…

Excel大文件拆分

import pandas as pddef split_excel_file(input_file, output_prefix, num_parts10):# 读取Excel文件df pd.read_excel(input_file)# 计算每部分的行数total_rows len(df)rows_per_part total_rows // num_partsremaining_rows total_rows % num_partsstart_row 0for i i…

微信小程序开发TABBAR及第三方接口调用程序

最终样式&#xff1a; 1、在微信小程序管理页面增加第三方调用接口 注意事项&#xff1a;必须是htts安全协议的接口 配置完成后在微信开发工具中可以看到配置的第三方接口URL 2、项目目录文件结构 3、程序代码 app.json {"pages": ["pages/home/home",&…

git -学习笔记

目录 基本操作语法 设置用户和邮箱 版本回退 工作区和暂存区 撤销修改 删除与恢复 一工作区删除了&#xff0c;但是暂存区没删除 二工作区误删了&#xff0c;暂存区还有 github-Git 连接 报错解决-push远程仓库被拒绝 远程库 分支 分支冲突 储藏分支 回到当前分…

谷云科技iPaaS×DeepSeek:构建企业智能集成的核心底座

2025年&#xff0c;DeepSeek大模型的爆发式普及&#xff0c;正引领软件行业实现 “智能跃迁”。从代码生成到系统集成&#xff0c;从企业级应用到消费级产品&#xff0c;自然语言交互能力已成为新一代软件的核心竞争力。据行业分析&#xff0c;超60%的软件企业已启动大模型适配…

UE Python笔记

插件 官方 商城 Python Editorhttps://www.fab.com/listings/f4c99ba0-1a86-4f6a-b19d-2fd13f15961b GitHUB 好像只更新到了2020年4.2x的版本。可能有大佬改了5.x的版本。也希望分享给我一份。谢谢 https://github.com/20tab/UnrealEnginePython 学习笔记 网上教程一大堆。…

PXE批量网络装机与Kickstart自动化安装工具

目录 一、系统装机的原理 1.1、系统装机方式 1.2、系统安装过程 二、PXE批量网络装机 2.1、PXE实现原理 2.2、搭建PXE实际案例 2.2.1、安装必要软件 2.2.2、搭建DHCP服务器 2.2.3、搭建TFTP服务器 2.2.4、挂载镜像并拷贝引导文件到tftp服务启动引导文件夹下 2.2.5、编…

【C语言】第八期——指针、二维数组与字符串

目录 1 初始指针 2 获取变量的地址 3 定义指针变量、取地址、取值 3.1 定义指针变量 3.2 取地址、取值 4 对指针变量进行读写操作 5 指针变量作为函数参数 6 数组与指针 6.1 指针元素指向数组 6.2 指针加减运算&#xff08;了解&#xff09; 6.2.1 指针加减具体数字…

Linux系统管理(十七)——配置英伟达驱动、Cuda、cudnn、Conda、Pytorch、Pycharm等Python深度学习环境

文章目录 前言安装驱动下载安装Cuda编辑环境变量安装Cudnn安装conda验证安装成功配置conda镜像退出conda环境创建python环境查看当前conda环境激活环境安装python包安装pytorch 安装pycharm安装jupyter notebook 前言 深度学习和大语言模型的部署不免会用到Linux系统&#xff…

C++蓝桥杯基础篇(六)

片头 嗨~小伙伴们&#xff0c;大家好&#xff01;今天我们来一起学习蓝桥杯基础篇&#xff08;六&#xff09;&#xff0c;练习相关的数组习题&#xff0c;准备好了吗&#xff1f;咱们开始咯&#xff01; 第1题 数组的左方区域 这道题&#xff0c;实质上是找规律&#xff0c;…

计算机毕业设计Python+DeepSeek-R1大模型期货价格预测分析 期货价格数据分析可视化预测系 统 量化交易大数据 机器学习 深度学习

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

webstorm的Live Edit插件配合chrome扩展程序JetBrains IDE Support实现实时预览html效果

前言 我们平时在前端网页修改好代码要点击刷新再去看修改的效果&#xff0c;这样比较麻烦&#xff0c;那么很多软件都提供了实时预览的功能&#xff0c;我们一边编辑代码一边可以看到效果。下面说的是webstorm。 1 Live Edit 首先我们需要在webstorm的settings里安装插件Live …

可以免费无限次下载PPT的网站

前言 最近发现了一个超实用的网站&#xff0c;想分享给大家。 在学习和工作的过程中&#xff0c;想必做PPT是一件让大家都很头疼的一件事。 想下载一些PPT模板减少做PPT的工作量&#xff0c;但网上大多精美的PPT都是需要付费才能下载使用。 即使免费也有次数限制&#xff0…

九、数据治理架构流程

一、总体结构 《数据治理架构流程图》&#xff08;Data Governance Architecture Flowchart&#xff09; 水平结构&#xff1a;流程图采用水平组织&#xff0c;显示从数据源到数据应用的进程。 垂直结构&#xff1a;每个水平部分进一步划分为垂直列&#xff0c;代表数据治理的…