C++:多态(协变,override,final,纯虚函数抽象类,原理)

目录

编译时多态

函数重载

模板

运行时多态

多态的实现

实现多态的条件

协变

析构函数的重写

override 关键字

final 关键字

重载、重写、隐藏对比

纯虚函数和抽象类

多态的原理


多态是什么?

多态就是有多种形态

多态有两种,分别是编译时多态(静态多态)、运行时多态(动态多态)

编译时多态

函数重载

函数重载就是其中的一种多态

void print(int i) {  cout << "Printing int: " << i << endl;  
}  void print(double f) {  cout << "Printing float: " << f << endl;  
}  

我们用一个print函数就可以实现上面的两种状态,由于是编译时完成的多态所以叫做编译时多态 

模板

模板也是其中的一种多态

template<class T>  
void print(T value) {  cout << value << endl;  
} 

 这里我们可以给print函数传任意类型的参数,这里也可以体现出多态

在我们传参时在编译时期就会生成对应的函数,所以它也是编译时多态

运行时多态

运行时多态就是我们需要完成某一个任务(函数),那么当我们传不同的对象时所能产生的效果是不一样的

例如:

当我们买火车票的时候,普通人买票是全价,学生买票可以打折,如果是军人那甚至可以优先买票

当我们在完成买票这个过程的时候,不同的人来买票所产生的结果是不一样的,这就是多态

下面来具体解释运行时多态

多态的实现

多态的构成

首先多态是一个继承关系下的对象去调用同一个函数,从而产生了不同的行为

实现多态的条件

  • 被调用的函数必须是虚函数
  • 必须是指针或者引用调用虚函数
  • 派生类需要对基类的虚函数进行重写/覆盖(只有这样才会两个相同的函数有不同的行为)
class Person
{
public:virtual void BuyTicket(){cout << "买票全价" << endl;}
};class Student : public Person
{
public:void BuyTicket(){cout << "买票半价" << endl;}
};void func(Person* p)
{p->BuyTicket();
}int main()
{Person p;Student s;func(&p);func(&s);return 0;
}

首先Student和Person是继承关系

其次基类Person所需要实现多态的函数是虚函数

子类里面的函数可以写virtual也可以不写,因为Person继承下来后无论写不写本身都是虚函数

为了能有不同的行为,我们需要重写该函数

调用函数的时候使用的是指针或者引用

这样我们多态的条件就全部都有了,下面只需要传相应的对象就可以完成对应的行为了

协变

当派生类重写基类的虚函数时,派生类和基类的返回值不同

基类虚函数返回其它基类对象的指针或者引用,派生类虚函数返回其它派生类对象的指针或者引用,称为协变

class A {};
class B : public A {};class Person {
public:virtual A* BuyTicket(){cout << "买票-全价" << endl;return nullptr;}
};class Student : public Person {
public:virtual B* BuyTicket(){cout << "买票-打折" << endl;return nullptr;}
};void Func(Person* ptr)
{ptr->BuyTicket();
}int main()
{Person ps;Student st;Func(&ps);Func(&st);return 0;
}

析构函数的重写

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

如上代码A类的析构函数和B类的析构函数其实是构成重写的

看起来两个类的析构函数名字不相同,但是在编译器进行处理的时候会统一将析构函数的名字处理成destructor 

所以即使我们是两个A类型的指针,它们new出来的对象如果不构成多态是不会调用B的析构函数的,只有构成多态,A对象才会调用A的析构,B对象调用B的析构

这里已经满足了三个条件:

A和B的继承关系

析构函数为虚函数

指针或引用调用虚函数 

如果这里不构成多态那么就会发生内存泄漏的问题! 

override 关键字

由于C++对重写的要求较为严格,因此C++11提供了override关键字

它的作用是可以帮助我们检查是否重写

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

若是把A的virtual给去掉,则构不成重写

final 关键字

如果我们不想让派生类重写这个虚函数,那么我们可以加上关键字final

一旦我们试图对基类某个虚函数带上了final关键字进行重写,那么则会报错

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

重载、重写、隐藏对比

纯虚函数和抽象类

在虚函数的后面写上=0,则这个函数为纯虚函数

class Person
{
public:virtual void BuyTicket() = 0;
};

纯虚函数不需要定义实现

因为实现没有意义,需要被派生类重写。只需要声明即可 

这个时候的这个Person类叫做抽象类

有纯虚函数的类就是抽象类

抽象类是不可以定义出对象的 

如果派生类继承后不重写虚函数,那么该派生类也是抽象类

纯虚函数某种程度上强制了派生类重写虚函数,因为不重写就无法实例化出对象

多态的原理

当一个类中有虚函数时,这个类是会多出一个成员指针变量__vfptr,我们把它叫做虚函数表指针

v代表virtual,f代表function,ptr则是指针

class Person
{
public:virtual void BuyTicket(){cout << "买票全价" << endl;}
private:int _age;char _name[20];
};int main()
{Person p;cout << sizeof(p) << endl;return 0;
}

从上面的实验可以看出,类中是会有一个虚函数表指针的,除了age和name占24个字节以外,还多了一个虚函数表指针占了4个字节,所以是28个字节

从底层的角度来想,为什么我们满足了多态的条件后,可以通过Person指针所指向的对象来精确的找到我们要使用的函数呢? 

class Person
{
public:virtual void BuyTicket(){cout << "买票全价" << endl;}
private:int _age;char _name[20];
};class Student : public Person
{virtual void BuyTicket(){cout << "买票半价" << endl;}
private:int _id;
};int main()
{Person p;Student s;return 0;
}

通过上图得知

当我们满足多态的条件时,底层不再是编译时通过对象确定函数的地址,而是运行时到指向的对象的虚表中确定对应的虚函数地址,所以就能实现我们是哪个对象就调用哪个对象的类中的虚函数

这也是为什么它是运行时多态的原因

虚函数表

  • 基类对象的虚函数表中存放基类所有虚函数的地址
  • 派生类会继承基类的虚函数表指针,但两者并不是同一个虚函数表指针
  • 派生类的虚函数表中包含 
  1. 基类的虚函数地址
  2. 派生类重写的虚函数地址
  3. 派生类自己的虚函数地址
  • 派生类重写基类的虚函数后,派生类的虚函数表里对应的虚函数就会被覆盖成派生类重写的虚函数地址
  • 虚函数和普通函数一样,都是存在代码段中的,只是虚函数的地址又存在虚表中
  • 虚函数表存在哪个地方并没有严格的规定,由编译器自己决定,具体在哪里我们可以依靠代码验证

验证代码: 

class Person
{
public:virtual void BuyTicket(){cout << "买票全价" << endl;}void func(){cout << "void func()" << endl;}
private:int _age;char _name[20];
};class Student : public Person
{virtual void BuyTicket(){cout << "买票半价" << endl;}
private:int _id;
};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);Person b;Student d;Person* p3 = &b;Student* p4 = &d;printf("Person虚表地址:%p\n", *(int*)p3);printf("Student虚表地址:%p\n", *(int*)p4);printf("虚函数地址:%p\n", &Person::BuyTicket);printf("普通函数地址:%p\n", &Person::func);return 0;
}

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

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

相关文章

黑马智数Day1

src文件夹 src 目录指的是源代码目录&#xff0c;存放项目应用的源代码&#xff0c;包含项目的逻辑和功能实现&#xff0c;实际上线之后在浏览器中跑的代码就是它们 apis - 业务接口 assets - 静态资源 &#xff08;图片&#xff09; components - 组件 公共组件 constants…

LeetCode 面试经典 150 题回顾

目录 一、数组 / 字符串 1.合并两个有序数组 &#xff08;简单&#xff09; 2.移除元素 &#xff08;简单&#xff09; 3.删除有序数组中的重复项 &#xff08;简单&#xff09; 4.删除有序数组中的重复项 II&#xff08;中等&#xff09; 5.多数元素&#xff08;简单&am…

【全网最全】2024年华为杯研赛A题成品论文获取入口(后续会更新)

您的点赞收藏是我继续更新的最大动力&#xff01; 一定要点击如下的卡片&#xff0c;那是获取资料的入口&#xff01; 点击链接加入【2024华为杯研赛资料汇总】&#xff1a;https://qm.qq.com/q/hMgWngXvcQhttps://qm.qq.com/q/hMgWngXvcQ你是否在寻找数学建模比赛的突破点&am…

LabVIEW机动车动态制动性能校准系统

机动车动态制动性能测试系统通过高精度的硬件设备与LabVIEW软件的紧密配合&#xff0c;实现了对机动车制动性能的精确校准与评估。系统不仅提高了测试的精确性和效率&#xff0c;而且具备良好的用户交互界面&#xff0c;使得操作更加简便、直观。 项目背景 随着机动车辆数量的…

Redisson实现分布式锁(看门狗机制)

目录 可重入锁&#xff1a; 锁重试和看门狗机制&#xff1a; 主从一致性&#xff1a; 首先引入依赖&#xff0c;配置好信息 3.使用Redisson的分布式锁 可重入锁&#xff1a; 可重入锁实现是通过redsi中的hash实现的&#xff0c;key依旧是业务名称加id&#xff0c;然后第一个…

Cocos Creator3.x设置动态加载背景图并且循环移动

效果图 项目结构 项目层级结构&#xff1a; 预制&#xff1a; 代码 import { _decorator, CCFloat, Component, Node, Sprite, instantiate, Prefab, assert } from cc; const { ccclass, property } _decorator;/*** 背景脚本*/ ccclass(Background) export class Backg…

前端入门:HTML+CSS

引言&#xff1a; 前端三大件&#xff1a;HTML、CSS、JS&#xff0c;每一个部分都很重要&#xff0c;我听过比较形象的比喻就是HTML&#xff08;HYPER TEXT MARKUP LANGUAGE&#xff09;相当于骨架&#xff0c;而CSS就是装饰渲染&#xff0c;JS则是动作功能实现。 之前的文章…

Qt 模型视图(三):视图类QAbstractItemView

文章目录 Qt 模型视图(三):视图类QAbstractItemView1.基本概念1.1.使用现有视图1.2.使用模型1.3.使用模型的多个视图1.4.在视图之间共享选择 Qt 模型视图(三):视图类QAbstractItemView ​ 模型/视图结构是一种将数据存储和界面展示分离的编程方法。模型存储数据&#xff0c;视…

Unity3D入门(一) : 第一个Unity3D项目,实现矩形自动旋转,并导出到Android运行

1. Unity3D介绍 Unity3D是虚拟现实行业中&#xff0c;使用率较高的一款软件。 它有着强大的功能&#xff0c;是让玩家轻松创建三维视频游戏、建筑可视化、实时三维动画等互动内容的多平台、综合型 虚拟现实开发工具。是一个全面整合的专业引擎。 2. Unity安装 官网 : Unity…

感知笔记4:YOLO 3D 物体定位

如何在 ROS 中使用 YOLO 如何在 2D 中检测人和大象如何在 3D 中检测人和大象 有许多可用的深度学习库。您可能听说过&#xff1a;Keras、TensorFlow 和 Darknet。 在这里我们将使用 Darknet&#xff0c;因为它实现了 YOLOv3&#xff0c;这是一个对象检测模型。它非常快&…

【AI大语言模型】提示词工程基础及进阶

【AI大语言模型】提示词工程基础及进阶 什么是提示词&#xff08;Prompt&#xff09; 提示词&#xff08;Prompt&#xff09;是与人工智能对话时用来引导或触发生成某种输出的指令。 它相当于你在与 AI 进行互动时所提出的问题或任务说明。提示词可以是简短的指令、一个问题…

YOLOv8改进,YOLOv8替换主干网络为VanillaNet( CVPR 2023 华为提出的全新轻量化架构),大幅度涨点

改进前训练结果: 改进后训练结果: 摘要 基础模型的核心理念是“更多即不同”,这一理念在计算机视觉和自然语言处理领域取得了惊人的成功。然而,变压器模型的优化挑战和固有复杂性呼唤一种向简化转变的范式。在本研究中,引入了VanillaNet,一种拥抱设计优雅的神经网络架构…

VSCode好用的插件推荐

1. Chinese 将vscode翻译成简体中文 如果安装了依然是英文&#xff0c;请参考如下方法&#xff1a; ctrlshfitp 2. ESLint 自动检查规范 3. Prettier - Code formatter 可以自动调整代码的缩进、换行和空格&#xff0c;确保代码风格统一。通过配置&#xff0c;Prettier可…

掌握Android开发新趋势:Jetpack与现代架构技术全解析

随着Android开发技术的不断进步&#xff0c;Jetpack和现代架构技术已成为构建高效、可维护应用的关键。本文将为您介绍一套全面的学习资料&#xff0c;包括大纲、PDF文档、源代码以及学习视频&#xff0c;帮助您深入理解Jetpack核心库、架构组件以及现代开发工具。 内容&#…

python贪吃蛇游戏项目源码【免费】

使用Pygame库实现的贪吃蛇游戏。Pygame是一个用于创建视频游戏的Python模块集合&#xff0c;它提供了图形和声音库&#xff0c;使游戏开发变得容易。 初始化设置 屏幕大小 (SCREEN_WIDTH, SCREEN_HEIGHT): 定义了游戏窗口的宽度和高度。方格大小 (SIZE): 定义了游戏中每个小方…

简单水印通过python去除

简单水印通过python去除 先看效果&#xff0c;如果效果不是你需要的就可以不用浪费时间。 注意&#xff1a;这种主要还是对应的文字在水印上方的情况&#xff0c;同时最好不要有渐变水印否则可能最后输出的图片的水印还会有所残留&#xff0c;不过还是学习使用&#xff0c;相信…

救生圈检测系统源码分享

救生圈检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Visio…

基于协同过滤算法的商品推荐系统

系统展示 用户前台界面 管理员后台界面 商家后台界面 系统背景 随着互联网技术的飞速发展&#xff0c;用户每天面临的信息量呈爆炸式增长&#xff0c;如何有效地筛选出用户感兴趣的内容成为一大挑战。在此背景下&#xff0c;基于协同过滤算法的商品推荐系统应运而生。该系统通过…

九章云极DataCanvas公司荣获2024年服贸会“科技创新服务示范案例”

9月15日&#xff0c;2024年中国国际服务贸易交易会&#xff08;服贸会&#xff09;示范案例交流会暨颁奖典礼在北京国家会议中心举行&#xff0c;九章云极DataCanvas 公司自研的DataCanvas Alaya NeW智算操作系统凭借卓越的AI创新实力、前瞻性的市场布局以及突破性的技术革新成…

Golang | Leetcode Golang题解之第423题从英文中重建数字

题目&#xff1a; 题解&#xff1a; func originalDigits(s string) string {c : map[rune]int{}for _, ch : range s {c[ch]}cnt : [10]int{}cnt[0] c[z]cnt[2] c[w]cnt[4] c[u]cnt[6] c[x]cnt[8] c[g]cnt[3] c[h] - cnt[8]cnt[5] c[f] - cnt[4]cnt[7] c[s] - cnt[6]…