C++ 多态详解

news2024/12/24 0:08:29

目录

多态的概念

定义

 C++直接支持多态条件

 举例

回顾继承中遇到的问题

 虚函数-虚函数指针-虚函数列表

虚函数

虚函数指针

虚函数列表

虚函数调用流程

虚函数于普通成员函数的区别


多态的概念

定义

  多态:相同的行为方式导致了不同的行为结果,同一行语句展现了多种不同的表现形态,即多态性。C++多态,父类的指针可以指向任何继承于该类的子类对象,父类指针具有子类的表现形态,多种子类表现为多种形态由父类指针统一管理,那么这个父类指针就具有了多种形态,即多态。

 C++直接支持多态条件

1.在继承关系下,父类指针指向子类对象(而非子类指针指向子类对象),通过该指针调用虚函数。

2.父类中存在虚函数(virtual修饰),且子类中重写了父类的虚函数。

重写:在继承条件下,子类定义了与父类中虚函数一摸一样的函数(包括:函数名、参数列表、返回值)我们称之为重写。

 举例

class CFather {
public:
	//虚函数 virtual: 定义虚函数的关键字
	virtual void fun() {
		cout << __FUNCTION__ << endl;
	}
};
class CSon :public CFather {
public:
	void fun() {  //子类的函数一旦重写了父类的虚函数,即使不加关键字,也会被认定为是虚函数
		cout << __FUNCTION__ << endl;
	}

};
int main() {

	CFather* pFa = new CSon;//父类指针指向子类对象
	pFa->fun();

	CSon* pSon = new CSon;//不叫多态
	pSon->fun();

    return 0;
}

 用子类指针调用出子类对象不叫多态。

回顾继承中遇到的问题

还记得我们在继承中遇到这样一个问题,我们无法通过父类指针调用子类中不统一的函数,而最后我们是通过类成员函数指针来解决这个问题的,不过实现的过程也是十分的艰难。如今我们学了多态,那么这个问题解决起来就十分简单了。

我们直接在父类中创建一个eat虚函数,使子类中的eat也变为虚函数,那么他就可以指向子类中的不统一的函数了。

class CPeople {
public:
	int m_money;
	CPeople() :m_money(100) {}
	void cost(int n) {
		cout << __FUNCTION__ << endl;
		m_money -= n;
	}

	void play() {
		cout << "溜溜弯" << endl;
	}

	void drink() {//如果增加公共的功能,属性,只需要在父类中增加一份即可
		cout << "喝饮料" << endl;

	}

	virtual void eat() {

	}
};

class CWhite : public CPeople {

public:
	//CWhite():m_money(100){}

	void eat() {
		cout << "吃牛排" << endl;
	}


};

class CYellow : public CPeople {
public:

	void eat() {
		cout << "小烧烤" << endl;
	}

};

class CBlack : public CPeople {
public:

	void eat() {
		cout << "西瓜炸鸡" << endl;
	}

};

 调用的时候就可以直接用父类指针指向子类成员函数了

void fun(CPeople* pPeo) {
	pPeo->cost(10);
	//(pPeo->*p_fun)(); //类成员函数指针
	pPeo->eat();  //多态解决
	pPeo->play();
	pPeo->drink();
}
int main() {	
    fun(new CBlack);
	fun(new CYellow);

	return 0;
}

 虚函数-虚函数指针-虚函数列表

虚函数

定义虚函数使用关键字virtual,虚函数是实现多态必不可少的条件之一。

我们知道,如果创建的类为空类,那么这个类所占用的空间为1个字节,并且如果在这个类中创建一个函数,那么此时类所占空间仍为1个字节,因为普通函数不会占用类的空间。但是如果我们在类中创建一个或多个虚函数,那此时类所占的空间就为4个字节了,那是为什么呢?

class CTest {
public:
	void fun() {
		cout << __FUNCTION__ << endl;
	}
	virtual void fun1() {
		cout << __FUNCTION__ << endl;
	}
    	virtual void fun2() {
		cout << __FUNCTION__ << endl;
	}

};
int main() {
	cout << sizeof(CTest) << endl;

	return 0;
}

 由于我们不管创建多少个虚函数类占的内存空间都为4,所以可以得出占的这个内存与虚函数有关,但是与虚函数的数量无关。

我们通过调试器发现,在局部变量中多出了一个名为__vfptr二级指针,由于我的系统为x86 32位操作系统,所以这个指针占的字节为4,那么这个指针就是虚函数指针。

 虚函数指针

__vfptr (虚函数指针):在一个类中,当存在虚函数时,在定义对象的内存空间的首地址会多分配出一块内存,在这块内存中增加一个指针变量(二级指针 void**),也就是虚函数指针。

 

 · 属于对象的,由编译器默认添加,可以看作是一般的成员属性。

· 定义对象时才存在(内存空间得以分配),多个对象多份指针。

· 指向了一个函数指针数组(虚函数列表,vftable)。

· 每个对象中的虚函数指针指向的是同一个虚函数列表。

· 定义对象调用构造函数,执行初始化参数列表时,被初始化才指向了虚函数列表。

class CTest {
public:
	//int m_a;
	CTest()/* : __vfptr(vftable) */ /*:m_a(1)*/{
		cout << __FUNCTION__ << endl;
	}
	void fun() {
		cout << __FUNCTION__ << endl;
	}
	virtual void fun1() {
		cout << __FUNCTION__ << endl;
	}
	virtual void fun2() {
		cout << __FUNCTION__ << endl;
	}
};
int main() {
	cout << sizeof(CTest) << endl;

	CTest tst;

	CTest tst2;

	return 0;
}

 注意:这里两个对象的虚函数指针是指向的地址相同,并不是他们俩本身的地址相同,不要弄混了。

 测试虚函数指针在对象内存空间的首地址被创建:

class CTest {
public:
	int m_a;
	CTest()/* : __vfptr(vftable) */ :m_a(1){
		cout << __FUNCTION__ << endl;
	}
	void fun() {
		cout << __FUNCTION__ << endl;
	}
	virtual void fun1() {
		cout << __FUNCTION__ << endl;
	}
	virtual void fun2() {
		cout << __FUNCTION__ << endl;
	}
};
int main() {
	cout << sizeof(CTest) << endl;

	CTest tst;

	CTest tst2;

	CTest tst3;
	cout << &tst3 << "  " << &tst3.m_a << endl;

	return 0;
}

 虚函数列表

虚函数列表(vftable):是一个函数指针数组,数组每个元素为类中虚函数的地址。

 

 · 属于类的,在编译期存在,为所有对象共享。

· 必须通过真实存在的对象调用,无对象或空指针对象无法调用虚函数。

	CTest* ptst = nullptr;
	ptst->fun();  //普通 可以调

	//ptst->fun2(); //虚函数 不能调  程序异常  虚函数指针要找到对象的首地址,但是对象指向空根本就没有地址

虚函数调用流程

1.定义对象获取对象内存首地址中的__vfptr。

2.间接引用找到虚函数指针指向的虚函数列表vftable。

3.通过下标定位到要调用的虚函数元素(虚函数地址)。

4.通过这个地址(函数入口地址)调用到了虚函数。

模拟虚函数调用过程:

	//*(int*)&tst == vfptr;

	typedef void (*P_FUN)();

	P_FUN p_fun1 = (P_FUN)((int*)(*(int*)&tst))[0];
	P_FUN p_fun2 = (P_FUN)((int*)(*(int*)&tst))[1];
	(*p_fun1)();
	(*p_fun2)();

 虚函数于普通成员函数的区别

· 调用流程不同:虚函数的调用流程相比普通成员函数而言复杂得多,这是他们的本质区别。

· 调用效率不同:普通的成员函数通过函数名(即函数入口地址)直接调用执行函数,效率高速度快,虚函数的调用需要虚函数指针-虚函数列表的参与,效率低,速度慢。

· 使用场景不同:虚函数主要用于实现多态,这一点是普通函数无法做到的。

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

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

相关文章

【翻译一下官方文档】之uniapp的.sync修饰符

先用一个案例引出.sync修饰符 就是这样一个场景 父组件直接修改状态A当然没问题&#xff0c;但是子组件不能直接修改状态A&#xff0c;因为单向数据流限制 单向数据流 uni-app官网 所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定&#xff1a;父级 prop 的更新会…

AFP vs SMB vs NFS: 谁是最好的数据传输协议?

目录 SMB: 什么是SMB 协议&#xff1f; NFS: 什么是NFS协议? AFP: 设么是AFP协议&#xff1f; 如何选择合适的传输协议&#xff1f; 场景1: 大型企业 场景2: 小型网站设计公司 场景3&#xff1a; Linux软件开发组 可以在互联网上使用这些协议吗? AFP vs SMB vs NFS …

Docker的安装和镜像容器的基本操作

Docker的安装和镜像容器的基本操作 Docker 概述Docker与虚拟机的区别namespace的六项隔离Docker核心概念 安装 DockerDocker 镜像操作搜索镜像获取镜像镜像加速下载查看镜像信息查看下载的镜像文件信息查看下载到本地的所有镜像根据镜像的唯一标识 ID 号&#xff0c;获取镜像详…

基于struts + spring + hibernate的题库与试卷管理系统源码

3需求分析和设计方案 3.1 题库管理 3.1.1 试题管理需求分析 试题管理是整个系统非常核心的模块&#xff0c;它基于知识点模块、章节模块、课程模块、题型管理模块完成的基础上的。其中核心元素是试题&#xff0c;通过试题将题库中的各模块连接起来。 试题管理分为题库录入和…

MyBatisPlus学习

官网&#xff1a;https://mp.baomidou.com/ MyBatis Plus&#xff0c;简化 MyBatis &#xff01; 1.概述 需要的基础&#xff1a;把我的MyBatis、Spring、SpringMVC就可以学习这个了&#xff01; 为什么要学习它呢&#xff1f;MyBatisPlus可以节省我们大量工作时间&#xff0…

随机模型预测控制(SMPC)——考虑概率约束(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 模型预测控制&#xff08;MPC&#xff09;又称为滚动时域控制和滚动时域控制&#xff0c;是一种强有力的工程应用技术。MPC的价…

springboot +flowable,处理 flowable 的用户和用户组(一)

一.简介 对于flowable是什么以及关于此框架的具体信息可以参看此项目的官方文档&#xff1a;https://www.flowable.org/docs/userguide/index.html Flowable is a light-weight business process engine written in Java.这是官网文档对此框架的完美解释&#xff1a;Flowable…

AIGC技术周报|清华、北邮新研究:让文生图AI更懂你

AIGC通过借鉴现有的、人类创造的内容来快速完成内容创作。ChatGPT、Bard等AI聊天机器人以及DallE 2、Stable Diffusion等文生图模型都属于AIGC的典型案例。「AIGC技术周报」将为你带来最新的paper、博客等前瞻性研究。 OpenAGI&#xff1a;当大模型遇见领域专家 “愿原力与大型…

ctfshow_WEB_web2 wp

前言 写这个是因为。。。我想摆烂&#xff0c;就去从最简单的题开始做了&#xff0c;想着交一道题是一道嘛&#xff0c;总之觉得这样做很适合欺骗安慰自己&#xff08;逃 然后我发现我错了&#xff0c;我第二道题就做了好久还没做出来&#xff0c;甚至最后去点开了hint…… ps…

多数据源事务

使用 DATASOURCE 模式后&#xff0c;可能一个操作涉及到多个数据源。例如说&#xff1a;创建租户时&#xff0c;即需要操作主库&#xff0c;也需要操作租户库。 考虑到多数据的数据一致性&#xff0c;我们会采用事务的方式&#xff0c;而使用 Spring 事务时&#xff0c;会存在…

FTP服务--文件传输协议

FTP服务--文件传输协议 一、FTP服务端口二、FTP服务主动模式与被动模式三、FTP服务配置方法设置匿名用户访问的FTP服务(最大权限)配置文件中常见字段的含义 一、FTP服务端口 FTP服务器默认使用TCP协议的20,21端口与客户端进行通信 20端口&#xff1a;用于建立数据连接&#x…

GDB调试的基本使用、GDB调试多进程

1. 编译时加选项-g&#xff0c;生成具有调试信息的程序 gcc -g test.c -o test 2. 启动GDB &#xff08;1&#xff09;启动GDB&#xff1a; gdb test &#xff08;2&#xff09;设置运行时参数&#xff1a;&#xff08;主函数中可接收运行时参数&#xff09; set args //…

设计模式实现之state模式

状态模式的定义&#xff1a;Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.&#xff08;当一个对象在状态改变时允许其改变行为&#xff0c;这个对象看起来像其改变了其类&#xff09;。 一个对象可以…

KVM Bridge 配置

目录 Bridge方式原理 网桥方式配置步骤 1、编辑修改网络设备脚本文件&#xff0c;增加网桥设备br0 2、编辑修改网络设备脚本文件&#xff0c;修改网卡设备ens33 3、重启宿主机查看配置 虚拟机配置 Bridge方式原理 如上图&#xff0c;网桥的基本原理就是创建一个网桥并将…

IOC使用Spring实现附实例详解

目录 一、相关导读 1. Maven系列专栏文章 2. Mybatis系列专栏文章 3. Spring系列专栏文章 二、前言 Spring简介 Spring体系结构 三、Spring实现IOC 1. 创建Maven工程&#xff0c;引入对应依赖 2. 创建实体类&#xff0c;Dao接口及实现类 3. 编写xml配置文件 4. 测试…

【翻译一下官方文档】之uniapp基础内容

目录 表单控件绑定 v-model v-model结合表单 easycom组件规范 传值 prop emit 表单控件绑定 v-model 你可以用 v-model 指令在表单 input、textarea 及 select 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇&#xff0c;但 v-mo…

如何高效提高倾斜摄影三维模型顶层合并的技术方法分析

如何高效提高倾斜摄影三维模型顶层合并的技术方法分析 1、倾斜摄影三维模型顶层合并 1.1倾斜摄影三维模型是一种基于倾斜摄影技术&#xff0c;通过多个角度拍摄同一区域的影像&#xff0c;利用计算机图像处理和三维重建技术生成的三维地理信息数据。由于一个大区域可能需要多块…

智能家居代码架构---简单工厂模式

(11条消息) 智能家居 (10) ——人脸识别祥云平台编程使用(编译libcurl库支持SSL&#xff0c;安装SSL依赖库libssl、libcrypto)openssl 依赖库行稳方能走远的博客-CSDN博客 看上面这个博客的往期文章 代码设计经验的总结&#xff0c;稳定&#xff0c;拓展性更强。一系列编程思…

倾斜摄影三维模型格式转换OSGB 到3Dtitles 实现的常用技术方法

倾斜摄影三维模型格式转换OSGB 到3Dtitles 实现的常用技术方法 倾斜摄影三维模型是一种用于建立真实世界三维场景的技术&#xff0c;常用于城市规划、土地管理、文化遗产保护等领域。在倾斜摄影模型中&#xff0c;OSGB格式和3Dtiles格式都是常见的数据格式。其中&#xff0c;OS…

IJKPLAYER源码分析-主结构

前言 本文主要分析IJKPLAYER源码软解主流程&#xff0c;硬解将另起一篇分析。所用IJKPLAYER版本号&#xff1a; #define IJKPLAYER_VERSION "f0.7.17-28-gd7040f97" 主结构 IJKPLAYER播放器的解协议、解复用、解码、音视频同步与显示播放&#xff0c;以及主要线程等…