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

news2024/11/17 7:53:09

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.coloradmin.cn/o/2171530.html

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

相关文章

【中级通信工程师】终端与业务(九):市场细分与选择

【零基础3天通关中级通信工程师】 终端与业务(九)&#xff1a;市场细分与选择 本文是中级通信工程师考试《终端与业务》科目第九章《市场细分与选择》的复习资料和真题汇总。本章的核心内容涵盖了市场细分的概念与方法、目标市场选择策略以及市场定位的原则和步骤。通过本节的…

​fl studio21.2.3.4004中文版永久2024最新下载安装图文详细使用教程​

随着数字音乐制作的快速发展&#xff0c;越来越多的音乐制作软件涌现出来&#xff0c;而FL Studio无疑是其中的佼佼者。作为一款功能强大、易于上手的音乐制作软件&#xff0c;FL Studio V21中文版在继承了前代版本优秀基因的基础上&#xff0c;进一步提升了用户体验&#xff0…

用于MRI重建的具有全局感受野的傅里叶卷积块|文献速递--基于多模态-半监督深度学习的病理学诊断与病灶分割

Title 题目 Fourier Convolution Block with global receptive field for MRI reconstruction 用于MRI重建的具有全局感受野的傅里叶卷积块 01 文献速递介绍 从欠采样的磁共振成像&#xff08;MRI&#xff09;信号中重建图像可以显著减少扫描时间并改善临床实践。然而&…

rk3588S 调试USB摄像头

问题: 客户的 usb 摄像头 接上 板卡上的 USB2.0 的接口是可以的,但是 接上 typec 接口上的 OTGUSB的时候 ,就会出现,无法识别USB的问题。 情况的说明: 先来看一下硬件。 这里的 typec 接口实际上 只用到了 otg USB的 两根线, 也就是 把TYPEC 当做 USB2.0 来用了。(通…

Cannot read properties of undefined (reading ‘upgrade‘)

前端开发工具&#xff1a;VSCODE 报错信息&#xff1a; INFO Starting development server...10% building 2/2 modules 0 active ERROR TypeError: Cannot read properties of undefined (reading upgrade)TypeError: Cannot read properties of undefined (reading upgrade…

vxe-table制作高亮刷新功能

start 记录一下 vxe-table 实现表格新增数据背景闪烁功能。 1. 效果 2. demo代码 <template><div id"app"><div click"tomato">点我新增数据 lazy_tomato</div><vxe-grid refxTable :height"height" :columns&quo…

原文翻译:Make Skeleton-based Action Recognition Model Smaller, Faster and Better

全网没找到一个完整的翻译&#xff0c;用chatgpt翻译如下&#xff0c;可能有的地方不够准确&#xff0c;推荐结合原文对照着看更。 摘要 尽管基于骨架的动作识别在近年来取得了巨大的成功&#xff0c;但大多数现有方法可能面临模型规模庞大和执行速度缓慢的问题。为了解决这个…

Apifox 9月更新|「动态值」全新升级、跨团队引用接口和测试场景、测试报告交互优化

Apifox 新版本上线啦&#xff01;&#xff01;&#xff01; 看看本次版本更新主要涵盖的重点内容&#xff0c;有没有你所关注的功能特性&#xff1a; 「动态值」全新升级 更强大、更灵活的数据模拟能力 支持智能代码补全动态值 测试报告交互优化 支持跨团队引用接口和测试场…

LLM大模型学习:开源大模型技术路线及趋势

MLNLP社区是国内外知名的机器学习与自然语言处理社区&#xff0c;受众覆盖国内外NLP硕博生、高校老师以及企业研究人员。 社区的愿景是促进国内外自然语言处理&#xff0c;机器学习学术界、产业界和广大爱好者之间的交流和进步&#xff0c;特别是初学者同学们的进步。 转载自…

数电基础(组合逻辑电路+Proteus)

1.组合逻辑电路 1.1组合逻辑电路的分析 1.1.1组合逻辑电路的定义 组合逻辑电路的定义 &#xff08;1&#xff09;对于一个逻辑电路&#xff0c;其输出状态在任何时刻只取决于同一时刻的输入状态&#xff0c;而与电路的原来状态无关&#xff0c;这种电路被定义为组合逻辑电路…

MySQL 之索引详解

想象一下&#xff0c;你正在图书馆寻找一本关于 MySQL 索引的书。图书馆里有成千上万本书&#xff0c;但没有目录。你只能一排一排、一本一本地找&#xff0c;直到找到你想要的书。这将会花费大量的时间&#xff01;数据库索引就像图书馆的目录一样&#xff0c;可以帮助数据库系…

什么是智享AI直播(三代)?一文带你全面解析!

什么是智享AI直播&#xff08;三代&#xff09;&#xff1f;一文带你全面解析&#xff01; 在当今这个数字化飞速发展的时代&#xff0c;技术的每一次革新都深刻地改变着我们的生活与工作方式。随着人工智能&#xff08;AI&#xff09;技术的不断成熟与普及&#xff0c;智享AI…

【mysql】千万级数据MySQL索引优化实例

【mysql】千万级数据MySQL索引优化实例 【一】场景描述【二】生成数千万条记录【三】原始sql分析【四】第一次优化&#xff1a;常规索引【五】第二次优化&#xff1a;覆盖索引【六】第三次优化&#xff1a;减少数据量【七】第四次优化&#xff1a;小表驱动大表【八】第五次优化…

蓝桥杯模块二:数码管的静态、动态实现

模块二训练 1.静态显示 一、数码管电路图 二、电路分析 1.数码管电路分析 端口分公共端和段码&#xff0c;先用公共端控制一个数码管&#xff0c;再用段码实现显示数字。共阳数码管公共端输入高电平&#xff0c;段码输入低电平实现点亮 2.锁存器 Y7控制段码&#xff0c;Y6控…

机器学习学习笔记-20240927

文章目录 一些简单的指令数据操作广播机制 标量&#xff0c;向量&#xff0c;矩阵的相互求导1. 标量对标量的求导2. 标量对向量的求导3. 向量对标量的求导4. 向量对向量的求导5. 矩阵对标量的求导6. 矩阵对向量的求导 链式求导法则YYDS求出损失函数偏导为0时的最优解w*1. 损失函…

卷轴模式商城APP开发指南

卷轴模式商城APP的开发是一项融合了技术创新、用户体验优化与商业策略实施的综合性工程。本文将从程序员的角度出发&#xff0c;详细介绍该类型应用的开发流程&#xff0c;涵盖从需求分析到后期维护的各个环节。 一、需求分析 首先&#xff0c;明确APP的核心功能需求&#xff…

从0-1搭建海外社媒矩阵,详细方案深度拆解

做买卖&#xff0c;好的产品质量固然是关键&#xff0c;但如何让更多的消费者知道&#xff1f;营销推广是必不可少的。在互联网时代&#xff0c;通过社交媒体推广已经成为跨境电商卖家常用的广告手段。 如何通过海外社交媒体矩阵扩大品牌影响力&#xff0c;实现营销目标&#…

又一篇Nature!可解释GNN今年持续发力,创新思路有时候就这么简单!

最近发现了一篇优秀的Nature子刊论文&#xff0c;作者提出了一种基于可解释GNN癌症基因分析新框架&#xff0c;在预测任务中实现了卓越的性能表现。 除此之外&#xff0c;还出现了很多可解释GNN的新研究&#xff0c;其中顶会不少&#xff0c;可见无论在学术界和工业界&#xf…

AES CCM详解

AES CCM是一种对数据进行加密及完整性检查的算法&#xff0c;主要用到AES中的CBC(完整性检查)和CTR(对明文进行加密)&#xff0c;除此之外&#xff0c;还涉及到对数据的格式化(本文着重阐述)。 文章目录 加密过程STEPS 解密及校验过程STEPS 格式化B0的构成B0解析举例AAD的格式化…

企业微信扫码登录

请求url 可以看到如下结果&#xff1a; 请求的URL是 reqauth.aspx&#xff0c;这是发起认证的第一步&#xff0c;这个请求的返回结果是一个 XML 数据&#xff0c;包含一个 ReqID&#xff0c;用户授权的地址 AuthUrl 以及查询结果的地址 ResultUrl。 如果直接访问这个地址&…