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的小伙伴可以关注+私信,看到必回哦。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024-09-27 buildroot C和语言将 中文的GBK编码转换为 UTF-8 的代码, printf 显示出来,使用 iconv 库去实现。

一、GBK 的英文全称是 "Guobiao Kuozhan"&#xff0c;意为 "National Standard Extended"。它是对 GB2312 编码的扩展&#xff0c;用于表示更多汉字和符号 GBK&#xff08;国标扩展汉字编码&#xff09;是一种用于简体中文和繁体中文字符的编码方式&#x…

Python 从入门到实战30(高级文件的操作)

我们的目标是&#xff1a;通过这一套资料学习下来&#xff0c;通过熟练掌握python基础&#xff0c;然后结合经典实例、实践相结合&#xff0c;使我们完全掌握python&#xff0c;并做到独立完成项目开发的能力。 上篇文章我们讨论了操作目录的相关知识。今天我们将学习一下高级文…

Mac系统Docker中SQLserver数据库文件恢复记录

Mac系统Docker中SQLserver数据库文件恢复记录 Mac想要安装SQLsever&#xff0c;通过docker去拉去镜像是最简单方法。 一、下载Docker Docker 下载安装&#xff1a; 需要‘科学上网’ 才能访问到docker官网。&#xff08; https://docs.docker.com/desktop/install/mac-ins…

C语言进阶版第12课—字符函数和字符串函数1

文章目录 1. 字符分类函数1.1 库函数iscntrl1.2 库函数isspace1.3 库函数islower和isupper 2. 字符转换函数3. strlen函数的使用和模拟实现3.1 strlen函数的使用3.2 strlen函数的模拟实现 4. strcpy函数的使用和模拟实现4.1 strcpy函数的使用4.2 strcpy函数的模拟实现 5. strca…

C++读取txt文件中的句子在终端显示,同时操控鼠标滚轮(涉及:多线程,产生随机数,文件操作等)

文章目录 &#x1f315;运行效果&#x1f315;功能描述&#x1f315;代码&#x1f319;mian.cpp&#x1f319;include⭐MouseKeyControl.h⭐TipsManagement.h &#x1f319;src⭐MouseControl.cpp⭐TipsManagement.cpp &#x1f315;运行效果 &#x1f315;功能描述 线程一&am…

web前端-CSS引入方式

一、内部样式表 内部样式表(内嵌样式表)是写到html页面内部,是将所有的 CSS 代码抽取出来,单独放到一个<styie>标签中。 注意: ① <style>标签理论上可以放在 HTML文档的任何地方&#xff0c;但一般会放在文档的<head>标签中 ② 通过此种方式&#xff0c;可…

开发提效的工具tabby快速入门

1.什么是tabby&#xff1f; Tabby is an open-source, self-hosted AI coding assistant. With Tabby, every team can set up its own LLM-powered code completion server with ease. 官方网站&#xff1a;https://tabby.tabbyml.com/ 2.tabby服务安装(Hugging Face Spaces…

虚幻引擎的三种输入模式和将控件显示到屏幕上

首先要知道一个概念 , HUD 和 Input 都是由 PlayerController 来控制的 而虚幻的Input控制模式有三种 Set Input Mode Game Only (设置输入模式仅限游戏): 视角会跟着鼠标旋转 , 就是正常游戏的模式 , 这也是游戏默认输入模式 Set Input Mode UI Only (设置输入模式仅限UI): …

【C++】 vector 迭代器失效问题

【C】 vector 迭代器失效问题 一. 迭代器失效问题分析二. 对于vector可能会导致其迭代器失效的操作有&#xff1a;1. 会引起其底层空间改变的操作&#xff0c;都有可能是迭代器失效2. 指定位置元素的删除操作--erase3. Linux下&#xff0c;g编译器对迭代器失效的检测并不是非常…

通信工程学习:什么是FDD频分双工

FDD:频分双工 FDD(频分双工,Frequency Division Duplexing)是一种无线通信技术,它通过将频谱划分为上行和下行两个不重叠的频段来实现同时双向通信。以下是FDD频分双工的详细解释: 一、定义与原理 定义: FDD是一种无线通信系统的工作模式,其中上行链路(从移动…

每日OJ_牛客_OR59字符串中找出连续最长的数字串_双指针_C++_Java

目录 牛客_OR59字符串中找出连续最长的数字串 题目解析 C代码1 C代码2 C代码3 Java代码 牛客_OR59字符串中找出连续最长的数字串 字符串中找出连续最长的数字串_牛客题霸_牛客网 题目解析 双指针&#xff1a; 遍历整个字符串&#xff0c;遇到数字的时候&#xff0c;用双…

坚果N1 Air高亮版对比当贝D6X高亮版:谁是2000元预算的投影仪王者?

当贝D6X高亮版新品升级&#xff0c;对于那些计划在这个时间点购买投影仪的用户来说&#xff0c;现在是个绝佳的时机&#xff01;特别是那些预算在两千元左右的&#xff0c;目前两千元左右的投影仪&#xff0c;无外乎两款产品&#xff0c;当贝D6X高亮版和坚果N1 Air高亮版&#…

常见区块链数据模型介绍

除了加密技术和共识算法&#xff0c;区块链技术还依赖于一种数据模型&#xff0c;它决定了信息如何被结构化、验证和存储。数据模型定义了账户如何管理&#xff0c;状态转换如何发生&#xff0c;以及用户和开发者如何与系统交互。 在区块链技术的短暂历史中&#xff0c;数据…

13年408计算机考研-计算机网络

第一题&#xff1a; 解析&#xff1a;OSI体系结构 OSI参考模型&#xff0c;由下至上依次是&#xff1a;物理层-数据链路层-网络层-运输层-会话层-表示层-应用层。 A.对话管理显然属于会话层&#xff0c; B.数据格式转换&#xff0c;是表示层要解决的问题&#xff0c;很显然答案…

怎样用云手机进行TikTok矩阵运营?

在运营TikTok矩阵时&#xff0c;许多用户常常面临操作复杂、设备过多等问题。如果你也感到操作繁琐&#xff0c;不妨考虑使用云手机。云手机具备丰富的功能&#xff0c;能够帮助电商卖家快速打造高效的TikTok矩阵。接下来&#xff0c;我们将详细解析这些功能如何提升你的运营效…

智能化转型新篇章:EasyCVR引领大型连锁超市视频监控进入AI时代

随着科技的飞速发展&#xff0c;视频监控系统在各行各业中的应用日益广泛&#xff0c;大型连锁超市作为人员密集、商品繁多的公共场所&#xff0c;其安全监控显得尤为重要。为了提升超市的安全管理水平、减少损失、保障顾客和员工的安全&#xff0c;引入高效、全面的视频监控系…

Meta震撼发布Llama3.2大规模模型

在2024.9.26的年Meta Connect大会上&#xff0c;Meta正式推出了Llama3.2模型&#xff0c;旨在提升边缘AI和视觉任务的能力。Llama3.2系列包括11亿和90亿参数的中型视觉模型&#xff0c;以及为移动设备优化的1亿和3亿参数的小型模型&#xff0c;并针对高通和联发科的硬件平台进行…

Navicat数据库管理工具实现Excel、CSV文件导入到MySQL数据库

1.所需要的工具和环境 navicat等第三方数据库管理工具云服务器中安装了 1Panel面板搭建的mysql数据库 2.基于 1Panel启动mysql容器 2.1 环境要求 安装前请确保您的系统符合安装条件&#xff1a; 操作系统&#xff1a;支持主流 Linux 发行版本&#xff08;基于 Debian / Re…

C#和数据库高级:虚方法

文章目录 一、抽象方法和抽象类中的思考1.1、回顾抽象方法的特点1.2、针对抽象方法问题的引出 二、虚方法的使用步骤2.1、虚方法重写方法的调用2.2、系统自带的虚方法2.3、重写Equals方法2.4、虚方法和抽象方法的比较 三、虚方法和抽象方法的联系3.1、ToString()方法的应用 一、…

ARM点灯---看手册

知识点&#xff1a; 一个程序可能会遇到内存泄漏问题&#xff0c;可能一次运行泄漏几M大小&#xff0c;执行几个小时才会泄漏到站崩溃&#xff0c;所以要查看是否有内存泄漏。 查看手册教程 0927-上午 视频1&#xff1a;25&#xff1b;00 硬件程序开发流程 最小系统:单片…