C++入门day5-面向对象编程(终)

C++入门day4-面向对象编程(下)-CSDN博客


本节是我们面向对象内容的最终篇章,不是说我们的C++就学到这里。如果有一些面向对象的基础知识没有讲到,后面会发布在知识点补充专栏,全都是干货满满的。

https://blog.csdn.net/u2396573637/category_12738259.html?fromshare=blogcolumn&sharetype=blogcolumn&sharerId=12738259&sharerefer=PC&sharesource=U2396573637&sharefrom=from_linkicon-default.png?t=O83Ahttps://blog.csdn.net/u2396573637/category_12738259.html?fromshare=blogcolumn&sharetype=blogcolumn&sharerId=12738259&sharerefer=PC&sharesource=U2396573637&sharefrom=from_link好了,废话不多说,我们开始本节的正文:三大特性之一----多态特性


初识:多态特性

多态的基本概念

多态是C++面向对象的三大特性之一。其实早在运算符重载那一章节我们就已经在接触多态了。只不过我们当时还不认识,看完下文你就知道,本节你已经是有基础傍身的“大白”了(反正不是小白)

运算符重载链接:C++入门day3-面向对象编程(中)-CSDN博客

多态分为两种:

静态多态:函数重载 和 运算符重载属于静态多态,复用函数名

动态多态:派生类和虚函数实现运行时多态

二者的区别:

静态多态:函数地址早绑定,编译时确定

动态多态:函数地址晚绑定,运行时确定

virtual关键字

C++中的virtual关键字主要有这样几种使用场景:第一,修饰父类中的函数 ;第二,修饰继承性。注意:友元函数、构造函数、static静态函数不能用virtual关键字修饰。普通成员函数和析构函数可以用virtual关键字修饰。

virtual具有继承性:父类中定义为virtual的函数在子类中重写的函数也自动成为虚函数。

一定要注意: 只有子类的虚函数和父类的虚函数定义完全一样才被认为是虚函数,比如父类后面加了const,如果子类不加的话就是隐藏了,不是覆盖.

函数重写(覆盖)

定义:子类重新定义父类中有相同名称返回值参数虚函数

class father{
public:virtual void speak(){cout<<"我是父亲"<<endl;}
};
class son:public father{
public:/*virtual*/ void speak(){cout<<"我是儿子"<<endl;}
}

 基本条件:

1.被重写的函数必须为vitual函数,并位于父类中

2.重写的函数与被重写的函数除了函数体可以不一样,其余的函数名、返回值、参数及类型都必须完全一致

如果我们不适用virtual关键字,分别在父类与子类写两个函数:查看son的内存布局

我们看不到任何东西存在。 

我们先在父类函数中加上virtual关键字,如上段代码,然后利用终端查看son类的内存布局情况:

在这里我们看到子类那里只有一个来自父类的虚函数表指针(virtual function-table ptr),而下面还附带一个son域内的虚函数表(virtual function table),里面有son::speak函数名。运行时自动检测是哪个类创建的对象调用的函数,这个过程就是根据虚函数表指针访问虚表然后找到被调函数的函数地址的过程。

当然,我们仍然可以通过加作用域的方式进行子类访问父类函数:

 函数隐藏

1.对于上文,如果父类子类之间有函数的函数名一致,其它不一定一致,那么会发生函数隐藏,此时子类创建的对象会优先匹配子类本身的函数。

2.如果函数要素完全一致:双方都没有virtual修饰,是函数隐藏。

多态案例分析

class father {
public:virtual void speak() {cout << "我是father" << endl;}void work() {cout << "上班" << endl;}
};
class son :public father {
public:void speak() {cout << "我是son" << endl;}void work() {cout << "上学" << endl;}
};
class daughter :public father {
public:void speak() {cout << "我是daughter" << endl;}void work() {cout << "嫁人" << endl;}
};

多态的基础 :需要有重写:子类重写父类的返回值、函数名、参数列表完全一致的虚函数。

(只要父类的函数是虚函数即可)

int main(){father *f1=new son;f1->speak();f1->work();father *f2=new daughter;f2->speak();f2->work();return 0;
}

动态多态:父类指针类型的变量或父类引用类型的变量,使用子类类型进行new创建。(即父指针指向子对象。)并通过该指针或引用调用子类的重写出来的虚函数的现象是动态多态 

动态的过程体现在,函数传参时,只要形参是父类指针或引用类型,那么传入子类时,就会自动使用子类类型的一系列重写的成员函数。其实就有点类似于局限版的模板了。

对于重写的函数,f会调用子类的重写函数,对于隐藏的函数,f会调用父类本身的函数。

小结

总结:

一、多态满足条件:

1.有继承关系

2.子类重写父类虚函数

二、多态使用条件:

父类指针或引用指向子类对象

多态的实现

C++为了实现多态,使用了一种动态绑定的技术,这个技术的核心内容就是虚函数表

虚函数表我们在上文也提到过,在这里我再放一下图大家有个基础的认识:

类的虚函数表

当子类中重写一个或多个父类的虚函数时,这些虚函数不会直接存在类内,而是添加一个数组--虚函数表,数组内存放的是函数的一个个虚函数指针 。

虚函数表:简称虚表

【⚪】虚表是一个存放指针的数组,内部元素是虚函数的指针。普通函数(非虚函数)调用不需要经过虚表,所以虚表的元素并不包括普通函数的函数指针。

【⚪】虚表内的条目--即虚函数指针,指针的赋值发生在编译阶段。也就是说在代码的编译阶段,虚表就已经构建出来了 

1. 每个包含了虚函数的类都包含一个虚表(存放虚函数指针的数组)

2.当子类继承父类时,子类会继承父类的函数的调用权。所以说如果一个父类包含了虚函数,那么子类也可以调用这些虚函数,(即上文提到的作用域指定访问)。换句话说,一个类继承了包含虚函数的基类,那么这个类也拥有了自己的虚表。

【⚪】虚表是属于类的,而不是属于某个具体的对象,一个类只有一个虚表,虚表这个数组就相当于static修饰的静态成员一样。同一个类的所有成员都使用同一个虚表。

虚表指针

虚表指针:即上文提到的虚函数表指针,用于访问类的虚表,一定程度上相当于隐藏的静态成员指针

类创建的对象通过虚表指针来访问类的虚表。简单来讲就是将数组的标准形式改为了指针形式。

        为了指定对象的虚表,对象内部包含一个虚表的指针,来指向自己所使用的虚表。为了让每个包含虚表的类的对象都拥有一个虚表指针,编译器在类中添加了一个静态成员指针变量:* vfptr,用来指向虚表,这样,当类的成员在创建时就拥有了这样的指针,且这个指针的值会自动被设置为指向类的虚表。

        验证vfptr指针的方法(_vfptr不可访问),就是用sizeof()先求一个普通类占的字节数大小,然后将类中的某一个函数前使用virtual修饰使其变为虚函数,再求该类占的字节数的大小,会发现多了四个字节,这就验证了vfptr的存在。当然也可以使用终端查看,方法如下

【关于如何使用终端查看类的布局教程-CSDN博客】

动态绑定

动态绑定我们会单独讲解,有需要可以到主页找一找,或者是在知识点补充专栏查找。如果没有找到请等待一两天,博主会加紧把文章码出来的。(专栏链接在文章开头)

纯虚函数与抽象类

纯虚函数

纯虚函数的语法:(当类中有了纯虚函数,这个类也被称为抽象类。)

virtual 返回值类型 函数名 (参数列表) = 0;

抽象类的特点:

1.无法实例化对象

2.子类必须重写抽象类的纯虚函数,否则也属于抽象类

Tips:虚函数在虚表中存放的是函数地址,而纯虚函数在虚表中存放的是0。

抽象类(接口)

        接口是为了描述类的行为和功能,不需要完成类的特定实现。C++的接口就是使用抽象类来实现的,抽象类与数据抽象互不混淆,数据抽象是一个把细节与相关数据分离的过程的这样一个概念。

        如果类中至少有一个纯虚函数,那么这个类就称为抽象类。语法同上。

        设计抽象类(Abstract-Class,ABC)的目的是为了给派生类提供一个行为的约束,必须要完成重写这些虚函数的功能才能自行拓展自身特殊行为。抽象类不能被实例化为对象,这一点使得抽象类可以很好的作为接口使用。相对的,非抽象类即为具体类

        抽象类的设计策略:面向对象的系统可能会使用一个抽象基类为所有的外部应用程序提供一个适当的、通用的、标准化的接口。然后,派生类通过继承抽象基类,把所有操作继承下来。

        这种架构具有很强的可拓展性。

虚析构与纯虚析构

回顾析构

在学习虚析构与纯虚析构之前。我们想一下普通的析构能解决什么问题?

防止对象的成员指针被重复释放时导致的无法释放nullptr的问题。

虚析构

虚析构是为了解决父类指针无法释放子类对象的问题的。

我们先来看一段代码:

#include<iostream>
using namespace std;class father abstract{
public:virtual void speak() = 0; virtual void work() = 0;virtual void show() = 0;~father() {cout << "father _destruct" << endl;}
};
class son :public father {
public:void speak() {cout << "我是son" << endl;}void work() {cout << "上学" << endl;}void show() {}~son() {cout << "son _destruct" << endl;}
};int main() {father* fs = new son;delete fs;return 0;
}

乍一看没啥问题,我们看一下运行结果: 

显然,只调用了父类的析构函数,子类的析构函数没有被调用。这就导致子类的资源得不到释放,这就造成了内存泄露的问题。

当我们在父类的析构函数之前加上virtual关键字后:

 

这时候二者的资源都被释放了。这才是我们想看到的结果。

虚析构:virtual ~类名(){}

纯虚析构:virtual ~类名()=0; 

虚析构与纯虚析构的区别:一旦类内有纯虚析构,类就是抽象类了,无法进行实例化了就。

 总结

1.虚析构或纯虚析构就是为了解决父类释放子类对象的问题的

2.如果子类中没有堆区数据,也可以不写虚析构或纯虚析构

3.拥有纯虚析构的类也属于抽象类

共性:都需要具体的函数实现;都可以解决父类指针释放子类对象的问题

区别:有纯虚析构的类是抽象类,无法实例化对象


遗留问题

        我们上节课遗留了一个问题,就是菱形继承问题。什么是菱形继承呢,简单说:你画个菱形,菱形的每个顶点都代表着一个类,其中第一层一个顶点,作为基类,第二层两个顶点,均由基类派生,第三层一个顶点,这个类继承第二层的两个类。继承关系构成了一个菱形,所以我们形象的称之为菱形继承。

菱形继承

上述的情况就是简单的菱形继承,代码如下:

class Animal{
public:int _age;void eat(){cout<<"eat"<<endl;}
};
class Wolf:public Animal{
public:int w_num;void speak(){cout<<"嗷呜~"<<endl;}
};
class Dog:public Animal{
public:int d_num;void speak(){cout<<"汪汪~"<<endl;}
};
class WolfDog:public Wolf,public Dog{
public:int _xxx;void show(){cout<<"我是狼狗"<<endl;}
};

         此时,我们的WolfDog的成员有哪些东西呢?首先wolf类继承了animal的_age属性,dog类也继承了_age属性,那么WolfDog继承Wolf和Dog两类时,同时继承了来自二者的_age属性。这样的话就会导致我们访问_age时,出现访问不明确的问题,我们要通过作用域限制明确访问。

此时,菱形继承带来了一个问题:二义性,还有数据冗余的问题。致使我们使用时非常不方便,因此C++提供了虚拟继承的技术来解决菱形继承带来的问题。 

虚拟继承

根据下图我们可以看出来,它的底层对象模型的布局与我们分析的相一致。这就是为什么普通的菱形继承会带来二义性及数据冗余的问题。

普通菱形继承的底层对象模型

虚拟继承的语法:

class A{};
class B:virtual public A{};

运用虚拟继承:

class Animal {
public:int _age;void eat() {cout << "eat" << endl;}
};
class Wolf :virtual public Animal {
public:int w_num;void speak() {cout << "嗷呜~" << endl;}
};
class Dog :virtual public Animal {
public:int d_num;void speak() {cout << "汪汪~" << endl;}
};
class WolfDog :public Wolf, public Dog {
public:int _xxx;void show() {cout << "我是狼狗" << endl;}
};

 查看底层模型我们可以知道,此时WolfDog类只有一个_age,直接继承来自Animal类的_age,第二层的两个类都是虚拟继承的Animal,可以理解为不算真正拥有。那它们怎么访问_age属性呢,这时候哦我们又看到了一个vbptr和vbtable,之前我们看到的是vfptr和vftable。后面这个我们认识,那这个vb到底是什么意思呢?

vbptr(virtual base-class-table pointer)虚基类表指针

vbtable(virtual base-class table)虚基类表

虚拟菱形继承的底层对象模型 

实际运用时很少用多继承语法,基本上不会遇到菱形继承问题,但需要我们理解底层,有助于你的实力提升。我们大多会使用  组合 的技术,即类内定义对象成员。 


感谢观看,如果有需要互3的小伙伴可以关注+私信,看到必回哦。

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

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

相关文章

小学生管理系统项目

在当今数字化教育的背景下&#xff0c;小学生管理系统应运而生。本项目采用 JSP Servlet JDBC MySQL 的技术组合&#xff0c;并在开发工具 Idea 和 Eclipse 的辅助下&#xff0c;结合数据库管理工具 Navicat 进行开发。 一、系统入口 用户登录入口&#xff1a;为普通用户提…

可看见车辆行人的高清实时视频第2辑

我们在《看见车辆行人的高清实时视频第2辑》分享了10处可看见车辆行人的实时动态高清视频。 现在我们又整理10处为你分享可看见车辆行人的实时动态高清视频&#xff0c;一共有30个摄像头数据&#xff0c;这些视频来自公开的高清摄像头实时直播画面。 我们在文末为你分享了这些…

低代码平台推荐与对比,国内外哪家更胜一筹?

低代码开发通过图形界面简化开发&#xff0c;提升速度与协作&#xff0c;降低成本。国内外平台如ZohoCreator、OutSystems等各具特色&#xff0c;支持快速开发、集成与数据安全。企业可试用后按需选择&#xff0c;降低决策成本。 一、低代码是什么&#xff1f; 低代码开发是一…

webpack 4 的 30 个步骤构建 react 开发环境

将 react 和 webpack4 进行结合&#xff0c;集 webpack 的优势于一身&#xff0c;从 0 开始构建一个强大的 react 开发环境。 其实很多人都有 一看就会&#xff0c;一做就废 的特点(当然也包括我在内)&#xff0c;这个时候&#xff0c;你需要制定一个略微详细的计划&#xff0…

C++的成员初始化列表

1、构造函数初始化列表&#xff0c;这是我们在构造函数中初始化类成员&#xff08;变量&#xff09;的一种方式&#xff0c;因此&#xff0c;当我们编写一个类并向该类添加成员时&#xff0c;通常需要用某种方式对这些成员&#xff08;变量&#xff09;进行初始化。 通常在构造…

Efficient DETR: Improving End-to-End Object Detector with Dense Prior

原文链接 [2104.01318] Efficient DETR: Improving End-to-End Object Detector with Dense Prior (arxiv.org)https://arxiv.org/abs/2104.01318 原文笔记 What 1、一种针对DETR的objectquery初始化的方法 2、针对Deformable DETR进行改进&#xff0c;改进之后的模型具有…

【工具分享】FONIX勒索病毒解密工具

前言 FONIX勒索软件首次出现在2020年6月&#xff0c;并迅速成为勒索即服务&#xff08;RaaS&#xff09;平台的一部分。尽管它最初的影响力有限&#xff0c;FONIX从2020年11月开始显著增加了攻击频率。FONIX以其复杂的加密方法著称&#xff0c;使用了AES、Salsa20、ChaCha和RS…

【HarmonyOS】自定义圆点进度条

【HarmonyOS】实现一个自定义带圆点的进度条效果。 方案就是做一个圆角组件&#xff0c;然后利用rotate旋转&#xff0c;至于动画效果&#xff0c;我查了一下文档&#xff0c;只要设置enableSmoothEffect:false&#xff0c;就可以关闭动画&#xff0c;然后自己开个定时器&#…

鸿蒙开发(NEXT/API 12)【硬件(接入手写套件)】手写功能开发

接入手写套件后&#xff0c;可以在应用中创建手写功能界面。界面包括手写画布和笔刷工具栏两部分&#xff0c;手写画布部分支持手写笔和手指的书写效果绘制&#xff0c;笔刷工具栏部分提供多种笔刷和编辑工具&#xff0c;并支持对手写功能进行设置。接入手写套件后将自动开启一…

C++:采用模板封装顺序表,栈,队列

1.顺序表&#xff1a; list.hpp #ifndef LIST_HPP #define LIST_HPP #include <iostream>using namespace std;template <class L>class Seqlist { private:L *ptr;L size;L len0;public:void init(L n){//堆区申请空间&#xff08;大小为n&#xff09;this->…

基于STM32的智能空气质量监测系统

目录 引言项目背景环境准备 硬件准备软件安装与配置系统设计 系统架构关键技术代码示例 传感器数据采集空气质量监测与控制实现实时数据显示与报警系统应用场景结论 1. 引言 空气质量对人类健康有着重要的影响&#xff0c;尤其是在污染较严重的地区。智能空气质量监测系统通…

点餐小程序实战教程12菜品展示

目录 1 点餐界面2 显示菜品分类2.1 创建变量2.2 数据绑定 3 显示菜品总结 我们上一篇介绍了数据源的设计方法&#xff0c;讲解了菜品分类和菜品数据源的创建以及后台功能的开发。有了后台功能并且准备好数据之后&#xff0c;我们就需要开发小程序部分。 现实中你看到的想到的绝…

大数据电商数仓项目--实战(一)数据准备

第一章 数仓分层 1.1 为什么要分层 1.2 数仓命名规范 1.2.1 表命名 ODS层命名为ods_表名DIM层命名为dim_表名DWD层命名为dwd_表名DWS层命名为dws_表名DWT层命名为dwt_表名ADS层命名为ads_表名临时表命名为tmp_表名 1.2.2 表字段类型 数量类型为bigint金额类型为decimal(16…

电脑usb接口封禁如何实现?5种禁用USB接口的方法分享!(第一种你GET了吗?)

“防患于未然&#xff0c;安全始于细节。”在信息技术飞速发展的今天&#xff0c;企业的信息安全问题日益凸显。 USB接口作为数据传输的重要通道&#xff0c;在带来便利的同时&#xff0c;也成为了数据泄露和安全风险的高发地。 因此&#xff0c;对电脑USB接口进行封闭管理&a…

微服务的优点及在云原生时代的合理落地方式

云计算de小白 那么&#xff0c;微服务到底能给业务带来什么好处&#xff1f;在云原生时代&#xff0c;如何更合理地实现微服务&#xff1f; 架构没有好坏之分&#xff0c;只有适合与不适合。然而&#xff0c;当我们对比微服务架构与单体架构时&#xff0c;可以发现微服务有以…

8--苍穹外卖-SpringBoot项目中套餐管理 详解(二)

目录 删除套餐 需求分析和设计 代码开发 根据id查询套餐 mapper层 Service层 ServiceImpl层 Mapper层 批量删除套餐 mapper层 Service层 ServiceImpl层 Mapper层 SetmealMapper.xml 修改套餐 需求分析和设计 代码开发 起售停售套餐 需求分析和设计 代码开发…

Docker全家桶:从0到加载本地项目

安装docker&#xff0c;我们选择的是CentenOS 7。 目录 Docker安装 命令 命令别名 数据卷挂载 Dockerfile 容器网络互联 Docker安装 1. 先删除本机旧的或者残留的docker sudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest …

前端——js基础

一、JavaScript &#xff08;简称js&#xff09;——js可以给网页实现一个动态效果 1.JavaScript 组成 - 核心语法 ECMScipt 简称(es): 规范js的基本语法 1.es是js的语法规范 管理者 2.js是es的实现 操作者 - DOM > 文档对象 提供js操作 (例如…

再也不用担心内容重复!在线伪原创工具,让创作更自由!

大家好&#xff0c;今天我们将讨论一个对网络写作非常有益的辅助工具——在线内容转换工具。不论您是需要更新您的博客&#xff0c;还是希望在社交平台上保持活跃&#xff0c;我们都频繁面临着迅速生成新内容的挑战。利用一个有效的工具来改写现有内容&#xff0c;可以极大地提…

什么是网络安全自动化以及优势与挑战

目录 网络安全自动化的工作原理 网络安全自动化的好处 增强的安全功能 改善表现和姿势 降低安全成本 简化的安全合规性和审计 更好的端点管理 网络安全自动化的挑战 耗时且容易出错的安全流程 可见性降低&#xff0c;风险和成本增加 合规管理 有用的网络安全自动化…