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.xdnf.cn/news/146399.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【前端学习】作用域实际问题学习记录

在复习apply&#xff0c;call&#xff0c;bind的时候遇到了一个作用域问题。 let name noName let age 18function getMyname() {console.log(my name is this.name, and I am this.age years old); }getMyname() }在全局使用let定义变量name和age之后&#xff0c;运行g…

★ C++进阶篇 ★ 二叉搜索树

Ciallo&#xff5e;(∠・ω< )⌒☆ ~ 今天&#xff0c;我将继续和大家一起学习C进阶篇第三章----二叉搜索树 ~ ❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️ 澄岚主页&#xff1a;椎名澄嵐-CSDN博客 C基础篇专栏&#xff1a;★ C基础篇 ★_椎名澄嵐的博客-CSD…

从零开始学习TinyWebServer

写在前面 项目参考&#xff1a;https://github.com/qinguoyi/TinyWebServer 写作框架/图参考&#xff1a;https://blog.csdn.net/qq_52313711/article/details/136356042?spm1001.2014.3001.5502 原本计划是&#xff0c;先将项目代码大概看一遍&#xff0c;然后再着手实现一下…

SOCKS5代理为何比HTTP代理更快?

在代理类型的选择上&#xff0c;SOCKS5代理经常被认为比HTTP代理更快&#xff0c;这是因为它们在工作原理和功能实现上存在较大的差异。让我们来探讨一下&#xff0c;为什么SOCKS5代理的速度通常比HTTP代理要快。 1. 协议的差异 SOCKS5代理&#xff1a;它是一个通用的代理协议…

【yolo破损纸板-包装盒-快递袋缺陷检测】

yolo破损纸板-包装盒-快递袋缺陷检测 破损纸质包装盒检测方盒型快递包裹检测 破损纸质包装盒检测 数据集合模型 可视化 方盒型快递包裹检测 数据集和模型 train: ../train/images val: ../valid/images test: ../test/images nc: 1 names: - box_packet可视化

初识linux(2)

接着上篇的初识linux(1)来接着说没看过的可以去看看 cp指令 语法&#xff1a;cp [选项] 源文件或目录 目标文件或目录 功能: 复制文件或目录 说明: cp指令用于复制文件或目录&#xff0c;如同时指定两个以上的文件或目录&#xff0c;且最后的目的地是一个已经存在的目录&#…

华为HarmonyOS地图服务 5 - 利用UI控件和手势进行地图交互

场景介绍 本章节将向您介绍如何使用地图的手势。 Map Kit提供了多种手势供用户与地图之间进行交互,如缩放、滚动、旋转和倾斜。这些手势默认开启,如果想要关闭某些手势,可以通过MapComponentController类提供的接口来控制手势的开关。 接口说明 以下是地图的控件和手势相…

安卓数据存储——SharedPreferences

共享参数 SharedPreferences 1、sharedPreferences是Android的一个轻量级存储工具&#xff0c;采用的存储结构是key - value的键值对方式 2、共享参数的存储介质是符合XML规范的配置文件。保存路径是&#xff1a;/data/data/应用包名/shared_prefs/文件名.xml 使用场景&…

OpenCV特征检测(7)角点检测函数goodFeaturesToTrack()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 确定图像上的强角点。 该函数根据 240中所描述的方法查找图像中最显著的角点或者指定图像区域内的最显著角点。 函数使用 cornerMinEigenVal 或…

线性表一(vector)

#include<bits/stdc.h> using namespace std; vector<int> a(5,2);//定义一个初始长度为5&#xff0c;每个元素值为2的可变数组 vector<char> b(3);//定义一个初始长度为3&#xff0c;每个元素为默认值的可变数组 vector<int> v;//定义一个长度为0的可…

go 读取excel数据存储到mysql

一、安装依赖 go get github.com/go-sql-driver/mysql go get github.com/jmoiron/sqlx 二、main.go package mainimport ("fmt""github.com/jmoiron/sqlx""log" ) import "github.com/tealeg/xlsx" import _ "github.com/go-s…

用户态缓存:链式缓冲区(Chain Buffer)

目录 链式缓冲区&#xff08;Chain Buffer&#xff09;简介 为什么选择链式缓冲区&#xff1f; 代码解析 1. 头文件与类型定义 2. 结构体定义 3. 宏定义与常量 4. 环形缓冲区的基本操作 5. 其他辅助函数 6. 数据读写操作的详细实现 7. 总结 8. 结合之前的内容 9. 具…

vue2+elementUI实现handleSelectionChange批量删除-前后端

功能需求&#xff1a;实现选中一个或多个执行批量删除操作 在elementUI官网选择一个表格样式模板&#xff0c;Element - The worlds most popular Vue UI framework 这里采用的是 将代码复制到前端&#xff0c;这里是index.vue <template><el-button type"dang…

人工智能有助于解决 IT/OT 集成安全挑战

思科的一项研究表明&#xff0c;信息技术 (IT) 和运营技术 (OT) 融合所带来的安全问题可以通过人工智能 (AI) 解决&#xff0c;尽管该技术也可能被恶意行为者利用。 该报告由思科和 Sapio Research 联合发布&#xff0c;对 17 个国家的 1,000 名行业专业人士进行了调查&#x…

如何在Java中实现用户列表的下载功能

在现代的Web应用中&#xff0c;用户管理是一个常见的需求。用户可能需要查看和下载他们的个人信息或者用户列表。本文将介绍如何使用Java和Spring框架实现用户列表的下载功能&#xff0c;具体采用Excel格式。 一、项目准备 首先&#xff0c;确保你的项目中已经引入了Spring B…

PAT甲级-1086 Tree Traversals Again

题目 题目大意 题目给出二叉树的节点个数&#xff0c;并给出用栈遍历树的过程。要求输出树的后序遍历&#xff0c;不能有多余空格。 思路 可以看出&#xff0c;栈遍历输出的是树的中序遍历&#xff0c;而依次push进栈的是先序遍历的顺序。题目要求后序&#xff0c;即已知先序…

MySQL缓冲池详解

Buffer Pool 本文参考开源项目&#xff1a;小林coding在线文档&#xff1b; 01-缓冲池概述 ​ 在MySQL查询数据的时候&#xff0c;是通过存储引擎去磁盘做IO来获取数据库中的数据&#xff0c;这样每次查询一条数据都要去做一次或者多次磁盘的IO&#xff0c;无疑是非常慢的。…

mxnet系统架构

mxnet系统架构 MXNet 是一个高性能、灵活的深度学习框架&#xff0c;最早由李沐&#xff08;Mu Li&#xff09;等人开发&#xff0c;并且得到了 Amazon 的支持。它支持多种语言&#xff08;包括 Python、Scala、C、R、Julia、Perl 等&#xff09;&#xff0c;并以其灵活的编程…

EfficientNet V1 V2

v1 论文地址&#xff1a;https://arxiv.org/abs/1905.11946 网络深度、宽度和图像分辨率&#xff0c;进行了栅格搜索&#xff08;Grid Search&#xff09;&#xff0c;找到了最优的几种搭配。 v2 论文地址&#xff1a;https://arxiv.org/abs/2104.00298 Fused-MBConv 到搜…

SpringBoot教程(三十) | SpringBoot集成Shiro权限框架(shiro-spring 方式)

SpringBoot教程&#xff08;三十&#xff09; | SpringBoot集成Shiro权限框架&#xff08;shiro-spring方式&#xff09; 一、 什么是Shiro二、Shiro 组件核心组件其他组件 三、流程说明shiro的运行流程 四、SpringBoot 集成 Shiro1. 添加 Shiro 相关 maven2. 添加 其他 maven3…