C++——多态调用和普通调用的本质区别

news2024/11/25 15:48:02

       目录      

一.多态特性

        回顾一下多态特性的含义:

        回顾多态特性的两大形成条件:

而普通调用和多态调用的本质区别在于:

二.理解调用

例一: 

        普通调用的理解1:

        普通调用的理解2:

        注:错误代码的使用

        普通调用的理解3:

 

        例二:注:例二与例一的父子类代码完全相同!

        多态调用的理解1:

代码解析:

        多态调用的理解2:

三.案例总结:


一.多态特性

        在学习多态特性的过程中,我们总是不明白什么样的函数调用就是多态调用,所以我们今天来梳理一下多态调用。       

        回顾一下多态特性的含义:

        多态!通俗来说,就是多种形态,具体上就是去完成某个行为,即当不同的对象去完成某个行为时会产生出不同的状态。

        回顾多态特性的两大形成条件:

1.被调用的函数必须是虚函数,且子类必须对父类的虚函数进行重写;
2.必须是父类指针指向子类对象,然后父类指针调用重写后的函数;

 

        之前我们在一般情况下调用的类成员函数,几乎都是普通调用,

而普通调用和多态调用的本质区别在于:

        多态调用:父类指针-->与指向的类或对象有关,指向的对象是什么类,就调用什么类的函数;

        普通调用:当对象调用函数时,与对象的类型有关,什么类型的对象或者指针调用函数,就调用什么类型的函数。

上面这几行内容是我们理解这两种调用的核心理念,所以我将会通过多个例子来帮助大家去理解多态调用和普通调用的本质。

二.理解调用

例一: 

        普通调用的理解1:
class Base {
public:
	virtual void Func1() {
		cout << "Base::Func1()" << endl;
	}

	 void Func3() {
		cout << "Base::Func3()" << endl;
	}

protected:
	string _name;
};

class Derive :public Base {
private:
	 virtual void Func1() {
		cout << "Derive::Func1()" << endl;
	}
	void Func3() {
		cout << "Derive::Func3()" << endl;
	}
};

int main() {
    cout << "普通调用1:" << endl;
    Base bb;
    Derive dd;	

	bb.Func1();
	bb.Func3();
	//以上两次都是父类对象调用自身的函数

	dd.Func1();
	dd.Func3();
    //以上两次都是子类对象调用自身的函数
	    return 0;

}

代码解析:

        这是两个不同类的类对象对各自成员函数调用,从而调用相应的重写函数。是类对象直接调用,并不符合多态特性形成的条件——这叫普通调用

        普通调用的理解2:

int main(){
    cout << "普通调用2:" << endl;

	Base bb;
	Derive dd;

	Derive* dtr = &dd;
	dtr->Func1();		//普通调用
	
    Base* bpr=&bb;
    bpr->Func1();       //普通调用

    return 0;
    }

代码解析:

        这两个类指针对象调用虽然都调用了虚函数Func1,但本质上是各类的指针对象去指向各自类的对象,进而调用相应的成员函数,所以这也是各类的指针对象调用各个类的函数,不符合多态特性形成的条件2——所以也是普通调用。

注:错误代码的使用
 Derive* dpr = &bb;	

        对于上面这句代码来讲是会报错的,因为编译器不支持将父类对象向下转换子类对象——也就是说子类指针不能指向父类对象。

 Derive* dpr = new Base();  

        这句代码报错的原因也是与上面的一样,子类指针去指向父类的匿名对象,也是不支持的.

        普通调用的理解3:

int main(){
    cout << "普通调用3:" << endl;
 
	Base bb;
	Derive dd;

 //父类指针指向子类对象
    Base* bbtr =&dd;
	bbtr->Func3();

    return 0;
    }

        父类指针指向子类的对象,符合多态形成的条件2,但也仅会指向子类对象中从父类继承过来的成员变量和成员函数,并不会指向子类对象自己新建的成员,因为bbtr调用Func3时,发现虚表中没有该函数,就确定了Func3并不是虚函数,采用的仍是普通调用(当对象调用函数时,与调用对象类型有关,类型是Base*,那么就调用Base类的Func3()函数!!!)。

        而bbtr得类型是父类Base,所以还是调用子类对象中从父类继承过来的Func3函数,所以结果是Base:Func3()

        以上就是在同一个案例中,不同的方式执行的普通调用,对于多态形成的两个条件而言,只要有一条不符合那就一定是普通调用!


 

例二:注:例二与例一的父子类代码完全相同!

        多态调用的理解1:

class Base {
public:
	virtual void Func1() {
		cout << "Base::Func1()" << endl;
	}

	 void Func3() {
		cout << "Base::Func3()" << endl;
	}

protected:
	string _name;
};

class Derive :public Base {
public:
	 virtual void Func1() {
		cout << "Derive::Func1()" << endl;
	}
	void Func3() {
		cout << "Derive::Func3()" << endl;
	}
};


int main() {

    Base b;
    Derive d;
    //多态调用
	cout << "多态调用1:" << endl;
	Base* btr = &b;
	btr->Func1();

	btr = &d;
	btr->Func1();

    return 0;
}
代码解析:

        Base* btr = &b;
        btr->Func1();

        由上可知,父类和子类各创建了一个对象而父类创建了一个指针,指向了父类对象,那么btr就会指向整个父类对象的地址,btr根据父类对象的地址找到vfptr(父类对象的虚表指针),调用函数Func1就是指针通过虚函数表中寻找该函数Func1的地址,找到后进行调用。 

代码解析:

    btr = &d;
    btr->Func1();

       多态调用完成(以上代码完全符合多态特性的形成条件)形成多态调用的基本要素:父类有指针指向父类自己的对象,且调用了虚函数,那么指向谁的对象,就调用谁的函数;

        父类创建的指针btr,又指向了子类对象的地址,那么btr指向的只是子类对象中从父类继承过来的那部分成员变量和函数罢了——切片思想!


       btr根据子类对象的地址找到了子类对象d的vfptr,因为子类Derive是继承的父类Base,那么父类的虚函数也会被继承下来,那么子类的虚函数表就是将父类的虚表拷贝一份到子类中,又因为子类重写了要调用的函数Func1,该函数的地址会进行更换,从而当btr调用函数时,从虚表中寻找该函数的地址,因为更换过Func1的地址,那么btr调用的就是子类的Func1了。

        多态调用的理解2:

        这是多态调用的另一种方式:父类指针指向类时,指向的是谁的类,调用的就是哪个类相应的函数。

int main(){
    cout << "多态调用2:" << endl;
	Base* btr2 =new Base;
	btr2->Func1();

	 btr2 = new Derive;
	btr2->Func1();
	cout << endl;
    return 0;
}


案例总结:

案例的难点在于多态调用1的例子:

        当父类的指针指向了父类对象的地址时,父类指针指向的是整个父类对象,而当父类的指针指向了子类对象的地址时,父类指针指向的仅是子类对象中从父类继承过来的成员变量与成员函数,所以当父类指针指向子类对象地址后,我来解释一下:结果所调用的函数为什么是父类的函数!例:

Base* bbtr=&dd;                //dd是子类对象  

bbtr->Func3();    

    bbtr调用的这个Func3函数有两个,从两个类的情况来看,一种是父类继承给子类的Func3(),一种是子类自己新建的Func3()。 而执行结果后,显示的是:指针调用了Base:Func3()

        对于bbtr指针的路线,它并不是去到父类中调用Func3,而是因为它指向了子类对象的地址,这个地址说具体就是指向了子类对象从父类继承过来的Func3函数地址,然后bbtr指针发现Func3并不是虚函数后,就直接去调用了子类对象中从父类继承过来的Func3(),因为bbtr是父类指针,它并不认识子类自己新建的Func3(),由于切片思想,它指向的那块虚表地址空间(虚表也是从父类继承过来的!)中并没有子类自己新建的Func3()  !!!   

        所以就是一句话,父类的指针在指向子类对象时,所调用的重写虚函数是如何能形成多态的,原因就在于父类指针能够轻松的的访问到子类的虚表地址空间中的相应虚函数。

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

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

相关文章

【mfc/VS2022】计图实验:绘图工具设计知识笔记

绘制曲线&#xff08;贝塞尔曲线&#xff09;&#xff1a; 转自&#xff1a;CDC 类 | Microsoft Learn 绘制一条或多条贝塞尔曲线。 BOOL PolyBezier(const POINT* lpPoints,int nCount);参数 lpPoints 指向包含曲线端点和控制点的 POINT 数据结构数组。 nCount 指定 lpPo…

移远通信C-V2X模组产品 助力车载生态建设跑出“加速度”

10月11日&#xff0c;移远通信受邀参加“长安创新驱动数智未来”行业研讨会。 本次会议围绕车载行业智能化转型取得的阶段性技术与终端成果展开讨论&#xff0c;旨在持续助力汽车行业智能出行的变革。移远通信产品总监侯海燕于会议上针对当下车载行业发展热点——C-V2X技术的相…

面经-北京泛微二面-Java开发

北京泛微二面-Java开发。以下内容为面试复盘&#xff0c;面试官问题无改动&#xff0c;回答已进行修正。 自我介绍 我是XXXX&#xff0c;来自XXX… 面试官提问&#xff1a; 1.像这些比赛的话&#xff0c;你是自己参加还是社团的人一起参加&#xff1f; 答&#xff1a;大多…

vue打包压缩

参考 https://www.cnblogs.com/lafitewu/p/8309305.html 注意:方法1和方法2不能同时用 取消打包的map config/index.js的productionSourceMap设置为false 抽取js 将部分常用又比较大的组件直接抽取为一个单独的js 打开webpack.base.conf.js,在module.exports.entry中添加想…

docker 搭建本地Chat GPT

要在CentOS7上安装Docker&#xff0c;您可以按照以下步骤进行操作&#xff1a; 1、更新系统包列表 sudo yum update2、安装Docker存储库的必要软件包 sudo yum install -y yum-utils device-mapper-persistent-data lvm23、添加Docker存储库 sudo yum-config-manager --add…

格式工厂怎么把两个视频合并在一起

免费的工具谁不喜欢呢&#xff0c;今天为大家介绍的是格式工厂这款多功能视频转换软件&#xff0c;然而今天主要为大家介绍的是格式工厂的视频合并功能。 是的&#xff0c;你没有听错&#xff0c;格式工厂除了转换之外&#xff0c;还可以视频合适、视频剪辑、视频分割、去水印…

Python3无法调用Sqlalchemy解决(mysqldb)

原因 在安装Sqlalchemy后运行程序报错 无法导入mysqldb&#xff0c;缺失模块 ImportError: No module named ‘MySQLdb’ 既然缺少 MySQLdb 这个模块&#xff0c;尝试按照正常的想法执行 pip install MySQLdbpip install mysql-python 应该能解决&#xff0c;但是却找不到…

MyBatis基础之自动映射、映射类型、文件注解双配置

文章目录 自动映射原理jdbcType同时启用配置文件和注解两种配置方式 自动映射原理 在 MyBatis 的配置文件&#xff08;settings 元素部分&#xff09;中&#xff0c;有一个 autoMappingBehavior 配置&#xff0c;其默认值为 PARTIAL &#xff0c;表示 MyBatis 会自动映射&…

SpringCloud学习笔记-Nacos的命名空间的配置

目录 1.创建新的NameSpace2.给XXX-service配置NameSpace3.Nacos管理页面管理NameSpace4.Eureka和Nacos区别 Nacos提供了namespace来实现环境隔离功能。 nacos中可以有多个namespacenamespace下可以有group、service等不同namespace之间相互隔离&#xff0c;例如不同namespace的…

Spring6 - ioc

文章目录 IoC容器IoC容器在Spring的实现基于XML管理Bean获取bean**①方式一&#xff1a;根据id获取**②方式二&#xff1a;根据类型获取③方式三&#xff1a;根据id和类型④扩展知识 依赖注入之setter注入依赖注入之构造器注入特殊值处理为对象类型属性赋值为数组类型属性赋值为…

【Qt】字体更大的富文本

使用size属性只能生成7个等级的字号&#xff0c;超过7的都视作为7。 当需要更加夸张的字号时则需要使用style属性&#xff0c;除此之外利用该属性可以生成更加逆天丰富的样式&#xff0c;(style属性是CSS样式表。 稍微跑题一下&#xff1a;似乎有安全性的考量&#xff0c;不少…

监控系列(六)prometheus监控DMHS操作步骤

一、监控的操作逻辑 给操作系统安装expect命令expect脚本执行dmhs_console脚本执行 cpt / exec 命令用脚本进行过滤字符串过滤dm_export读取脚本与当前日期作比较&#xff0c;然后返回差值 二、安装步骤 1. linux中Expect工具的安装及使用方法 https://blog.csdn.net/wangta…

网络工程师知识点

1、OSI模型是每层的功能&#xff0c;用到的协议&#xff0c;使用到的设备&#xff0c;涉及到的数据传输单元 第七层应用层&#xff1a;提供应用程序间通信 &#xff08;服务应用&#xff1a;http、ftp、dns) 第六层表示层&#xff1a;处理数据格式、数据加密等 第五层会话层&…

Redis 集群 Redis 事务 Redis 流水线 Redis 发布订阅 Redis Lua脚本操作

Redis 集群 & Redis 事务 & Redis 流水线 & Redis 发布订阅 Redis 集群linux安装redis主从配置查看当前实例主从信息 Redis Sentinelsentinel Redis Cluster Redis 事务Redis 流水线Redis 发布订阅Redis Lua脚本操作 Redis 集群 linux安装redis 下载安装包&#…

半监督学习介绍(为什么半监督学习是机器学习的未来)

文章目录 半监督学习的好处半监督学习原理半监督范式总结 半监督学习是一种利用标记和未标记数据的机器学习方法。半监督学习的目标是结合监督学习和无监督学习的优点&#xff1b;利用标记数据的准确性以及未标记数据的丰富性和较低成本。半监督学习可以被认为是 监督学习&…

[ubuntu]OpenFOAM国内源码满速下载地址

下列地址可直接使用git clone&#xff0c;例如&#xff0c;打开终端&#xff0c;在终端直接将下面的复制进去&#xff1a; git clone https://e.coding.net/dyfluid/ThirdParty-6/ThirdParty-6.git即可在本地创建ThirdParty-6文件夹。如果提示你没有git&#xff0c;那么输入下面…

OpenCV实现人脸关键点检测

目录 实现过程 1&#xff0c;代码解读 1.1 导入工具包 1.2导入所需图像&#xff0c;以及训练好的人脸预测模型 1.3 将 dlib 的关键点对象转换为 NumPy 数组&#xff0c;以便后续处理 1.4图像上可视化面部关键点 1.5# 读取输入数据&#xff0c;预处理 1.6进行人脸检测 1…

Django框架集成Celery异步-【2】:django集成celery,拿来即用,可用操作django的orm等功能

一、项目结构和依赖 study_celery | --user |-- models.py |--views.py |--urls.py |--celery_task |--__init__.py |--async_task.py |-- celery.py | --check_task.py | --config.py | --scheduler_task.py | --study_celery | --settings.py | --manage.py 依赖&#xff1a…

竞赛 深度学习+opencv+python实现昆虫识别 -图像识别 昆虫识别

文章目录 0 前言1 课题背景2 具体实现3 数据收集和处理3 卷积神经网络2.1卷积层2.2 池化层2.3 激活函数&#xff1a;2.4 全连接层2.5 使用tensorflow中keras模块实现卷积神经网络 4 MobileNetV2网络5 损失函数softmax 交叉熵5.1 softmax函数5.2 交叉熵损失函数 6 优化器SGD7 学…

HTTP Basic 认证

HTTP Basic 认证 难度等级&#xff1a;【初级】 由RFC7617定义的HTTP Basic认证是一种非常基础而简单的认证模式&#xff0c;因此叫他Basic认证。他本质上就是浏览器提供的一个接口&#xff0c;能够根据HTTP返回值&#xff0c;自动弹出一个登录框&#xff0c;让用户输入ID和密码…