C++多态 学习

目录

一、多态的概念

二、多态的实现

三、纯虚函数和多态类

四、多态的原理


一、多态的概念

        多态:多态分为编译时多态(静态多态)和运行时多态(动态多态)。编译时多态主要是我们之前学过的函数重载和函数模板,他们在传不同类型的参数就可以调用不同的函数,通过参数不同达到多种形态,之所以叫做编译时多态,是因为实参传给形参的参数匹配是在编译时完成的。

        运行时多态,就是去完成某个行为,可以传不同的对象完成不同的行为,就会达到多种的形态。例如买票行为,当普通人买票时,是全价买票;而当学生买票时,是半价买票。

二、多态的实现

1. 多态是一个继承关系下的类对象,去调用同意函数,产生了不同的行为。例如Student类继承了Person类,当我们调用同一个BuyTickets函数,Person对象全价买票,Student对象半价买票。

1.1 实现多态的条件

  • 必须是基类的指针或引用
  • 被调的函数必须是虚函数

1.2 虚函数

类成员函数前加 virtual 修饰,那么这个成员函数被称为虚函数。注意:非成员函数不能加virtual修饰。

class Base
{
public:
	virtual void func()
	{
		cout << "Base -> func()" << endl;
	}
};

这里的 virtual void func() 就是Base内的一个虚函数。

1.3 虚函数的重写/覆盖

派生类中有一个跟基类完全相同的虚函数(这里的完全相同指的是 返回值类型函数名参数列表完全相同),称为派生类的虚函数重写了基类的虚函数。

注意:在重写基类的虚函数时,对于派生类,我们其实可以不加 virtual 关键字,因为继承基类后,虚函数也被继承了下来,在派生类中依旧保持虚函数的属性,但是这种写法看起来不是很一目了然,但是如果不写的话,也是构成重写/覆盖的。

class Base
{
public:
	virtual void func1()
	{
		cout << "Base -> func1()" << endl;
	}
	virtual void func2()
	{
		cout << "Base -> func2()" << endl;
	}
	void func3()
	{
		cout << "Base -> func3()" << endl;
	}
};

class Derive : public Base
{
public:
	virtual void func1()
	{
		cout << "Derive -> func1()" << endl;
	}
	void func2()
	{
		cout << "Derive -> func2()" << endl;
	}
	void func3()
	{
		cout << "Derive -> func3()" << endl;
	}
};

上面Derive继承了Base,按照我们刚刚提到的,我们就可以得出结论,派生类Derive中的func1 和 func2是构成重写/覆盖的,而func3时不构成的。

1.4 多态场景的题目

以下程序输出的结果是什么(B)

A: A->0   B: B->1   C: A->1   D: B->0   E: 编译出错   F: 以上都不正确

class A
{
public:
	virtual void func(int val = 1)
	{
		cout << "A->" << val << endl;
	}
	virtual void test()
	{
		func();
	}
};

class B : public A
{
public:
	void func(int val = 0)
	{
		cout << "B->" << val << endl;
	}
};
int main()
{
	B* ptr = new B;

	ptr->test();
	return 0;
}

答案为什么是B呢?首先可以肯定的是,B中的func函数已经对A中的func函数实现了重写/覆盖。但是由于虚函数表的存在,当我们后面讲到虚表的时候,再来看这个问题,会更加的清楚。

三、纯虚函数和多态类

在虚函数的后面写上 =0 ,则这个函数称为纯虚函数,纯虚函数不需要定义实现,只要声明即可。包含纯虚函数的类称为抽象类,抽象类不能实例化出对象,如果派生类继承后不重写纯虚函数,那么派生类也是抽象类。纯虚函数某种程度上强制了派生类重写虚函数,否则实例不出来对象。例如:

class Base
{
public:
	virtual void func() = 0;
};

class Derive : public Base
{
public:
	virtual void func()
	{
		cout << "Derive -> func()" << endl;//重写func()
	}
};

int main()
{
	Base b;  //错误,因为Base是抽象类,不能实例化出对象
	Derive d; //正确,因为Derive重写了func()
	return 0;
}

四、多态的原理

1. 虚函数表指针

下面编译在32位程序的运行结果是什么 ( D )

A: 编译报错    B: 运行报错   C: 8    D: 12

class Base
{
public:
	virtual void func()
	{
		cout << "Base -> func()" << endl;
	}
protected:
	int _b = 1;
	char _ch = 's';
};

int main()
{
	Base b;
	cout << sizeof(b) << endl;

	return 0;
}

这是为什么呢,按内存对齐来看,一个int 和 一个char,算下来不应该是8吗?这是因为,除了_b和_ch成员,还多了一个_vfptr放在对象的前面(有的平台可能放在后面),对象中的这个指针我们叫做虚函数表指针。一个含有虚函数的类中都至少有一个虚函数表指针,因为一个类所有的虚函数的地址要被放到这个类对象的虚函数表中,虚函数表也称为虚表。如下图:

2. 多态的原理

//类省略未写

void Func(Person* p)
{
	p->BuyTickets();
}

int main()
{
	Person ps;
	Student st;
	Func(&ps);
	Func(&st);
	return 0;
}

从底层的角度Func函数中p->BuyTickets(),ptr是如何做到指向Person对象调用Person::BuyTickets,指向Student对象调用Student::BuyTickets的呢?通过上图我们可以看到,满足多态条件之后,底层不再是编译时通过调用对象确定函数的地址,而是运行时到指向的对象的虚表中确定对应虚函数的地址,这样就实现了指针或引用指向基类就调用基类的虚函数,指向派生类就调用派生类的虚函数。

如果派生类重写了基类的虚函数,那么派生类的虚函数表中对应的虚函数就会被覆盖成派生类重写的虚函数地址。

这里我们回头再看看1.4的题目:

class A
{
public:
	virtual void func(int val = 1)
	{
		cout << "A->" << val << endl;
	}
	virtual void test()
	{
		func();
	}
};

class B : public A
{
public:
	void func(int val = 0)
	{
		cout << "B->" << val << endl;
	}
};
int main()
{
	B* ptr = new B;

	ptr->test();
	return 0;
}

有了下面这张图,我们就很容易理解了,为什么最终的结果是B->1了。首先B是继承了A,并且B中的func函数满足了对基类的重写/覆盖,然后B也继承了A中的test()。因此当我们调用ptr->test()时,虚函数test()的地址在虚表中没有改变,调用的时候会去A类里面调用。之后才是调用func函数,此时已经构成重写/覆盖,因此调用的这个虚函数func()地址是指向B类的func()。那么有同学就说了,为什么不是B->0呢?我们要知道,重写/覆盖只是对函数的内容进行重写,因此相当于调用了下面的函数:

void func(int val = 1)
{
	cout << "B->" << val << endl;
}

你可以理解为重写/覆盖值改变了{ }内的内容,而函数参数还是用了基类的。

3. 虚函数表

  • 基类对象的虚函数表中存放基类所有虚函数的地址。
  • 派生类的虚函数表由两部分构成,继承下来的基类和自己的成员,一般情况下,继承下来的基类中中有虚函数表指针,自己就不会再生成虚函数表指针。但是继承下来的基类部分虚函数表指针和基类对象的虚函数表指针不是同一个。
  • 派生类中重写的基类的虚函数,派生类的虚函数表中对应的虚函数地址就会被覆盖生成派生类重写的虚函数地址。
  • 派生类的虚函数表中包含,基类的虚函数地址,派生类重写的虚函数地址,派生类自己的虚函数地址三个部分。
  • 虚函数表本质是一个存放虚函数指针的指针数组。
  • 虚函数存在于哪?虚函数和普通函数一样,编译好后是一段指令,都是存在代码段的,只是虚函数的地址又存到了虚表之中
  • 虚函数表存在于哪的呢?C++标准并没有规定,但是在VS里面是存在于常量区的。

我们可以通过下面的代码,在VS上证实:

int main()
{
	int i = 0;
	static int j = 1;
	int* p1 = new int;
	const char* p2 = "xxxxxxxx";
	printf("栈: % p\n", &i);
	printf("静态区: % p\n", &j);
	printf("堆: % p\n", p1);
	printf("常量区: % p\n", p2);
	Base b;
	Derive d;
	Base * p3 = &b;
	Derive * p4 = &d;
	printf("Base虚表地址: % p\n", *(int*)p3);
	printf("Derive虚表地址: % p\n", *(int*)p4);
	printf("虚函数地址: % p\n", &Base::func1);
	printf("普通函数地址: % p\n", &Base::func5);
	return 0;
}

运行结果显示虚表的地址和常量区的地址非常接近,而虚函数的地址和普通函数的地址非常接近。

以上内容如有错误,欢迎批评指正!

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

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

相关文章

OpenCV-Python笔记(上)

安装 全局安装 pip install opencv-python项目虚拟环境安装 # 进入项目根路径执行 .venv/bin/pip install opencv-python计算机眼中的图像 一张图片由大小比如&#xff08;100*100&#xff09;决定&#xff0c;说明存在100*100的像素点&#xff0c;每个像素点存在颜色通道&…

ppt文件怎么压缩变小一些?8种压缩PPT文件的方法推荐

ppt文件怎么压缩变小一些&#xff1f;在现代工作环境中&#xff0c;PPT文件常常是我们展示信息和分享想法的主要工具。然而&#xff0c;当这些文件变得庞大时&#xff0c;它们不仅会占用大量的存储空间&#xff0c;还可能导致处理速度变慢&#xff0c;影响整体工作效率。这种情…

4+1视图模型

逻辑视图&#xff08;Logical View&#xff09; 逻辑视图主要关注系统的功能分解&#xff0c;即系统如何被划分为不同的逻辑组件&#xff08;如类、接口、包等&#xff09;&#xff0c;以及这些组件之间的交互关系。它帮助开发者理解系统的业务逻辑和功能结构。 开发视图&…

【人工智能】OpenAI发布GPT-o1模型:推理能力的革命性突破,这将再次刷新编程领域的格局!

在人工智能领域&#xff0c;推理能力的提升一直是研究者们追求的目标。就在两天前&#xff0c;OpenAI正式发布了其首款具有推理能力的大语言模型——o1。这款模型的推出&#xff0c;不仅标志着AI技术的又一次飞跃&#xff0c;也为开发者和用户提供了全新的工具来解决复杂问题。…

【实践】应用访问Redis突然超时怎么处理?

目录标题 问题描述分析过程查看监控数据系统监控指标JVM监控指标Redis监控指标分析应用异常单机异常规律集群异常规律统计超时的key 初步结论验证结论访问Redis链路slowlogRedis单节点info all定位redis节点定位异常keybigkeystcpdump定位大key影响 经验总结 问题描述 某产品线…

【验收交付资料】系统培训方案(doc原件)

1. 培训目的 2. 培训方式 3. 培训内容 4. 培训讲师 5. 培训教材 6. 培训质量保证 软件全套资料部分文档清单&#xff1a; 工作安排任务书&#xff0c;可行性分析报告&#xff0c;立项申请审批表&#xff0c;产品需求规格说明书&#xff0c;需求调研计划&#xff0c;用户需求调查…

Pikachu靶场之XSS

先来点鸡汤&#xff0c;少就是多&#xff0c;慢就是快。 环境搭建 攻击机kali 192.168.146.140 靶机win7 192.168.146.161 下载zip&#xff0c;pikachu - GitCode 把下载好的pikachu-master&#xff0c;拖进win7&#xff0c;用phpstudy打开网站根目录&#xff0c;.....再用…

豆包MarsCode编程助手:产品功能解析与应用场景探索!

随着现代技术的不断进化升级&#xff0c;人工智能正在逐步改变着我们的日常工作方式。特别是对于复杂的项目&#xff0c;代码编写、优化、调试、测试等环节充满挑战。为了简化这些环节、提高开发效率&#xff0c;许多智能编程工具应运而生&#xff0c;豆包MarsCode 编程助手就是…

nodejs基础教程之-异步编程promise/async/generator

1. 异步 所谓"异步"&#xff0c;简单说就是一个任务分成两段&#xff0c;先执行第一段&#xff0c;然后转而执行其他任务&#xff0c;等做好了准备&#xff0c;再回过头执行第二段,比如&#xff0c;有一个任务是读取文件进行处理&#xff0c;异步的执行过程就是下面…

二、Kubernetes中pod的管理及优化

一 kubernetes 中的资源 1.1 资源管理介绍 在kubernetes中&#xff0c;所有的内容都抽象为资源&#xff0c;用户需要通过操作资源来管理kubernetes。 kubernetes的本质上就是一个集群系统&#xff0c;用户可以在集群中部署各种服务 所谓的部署服务&#xff0c;其实就是在kub…

【运维监控】Prometheus+grafana监控zookeeper运行情况

运维监控系列文章入口&#xff1a;【运维监控】系列文章汇总索引 文章目录 一、prometheus二、grafana三、prometheus集成grafana监控zookeeper1、修改zookeeper配置2、修改prometheus配置3、导入grafana模板4、验证 本示例通过zookeeper自带的监控信息暴露出来&#xff0c;然后…

Ceisum(SuperMap iClient3D for Cesium)实现平面裁剪

1&#xff1a;参考API文档&#xff1a;SuperMap iClient3D for Cesium 开发指南 2&#xff1a;官网示例&#xff1a;support.supermap.com.cn:8090/webgl/Cesium/examples/webgl/examples.html#layer 3&#xff1a;SuperMap iServer&#xff1a;欢迎使用 SuperMap iServer 11…

C语言---函数指针基础总结万字(4)

一、 函数 1.函数是一段可以重复执行的代码。 它可以接受不同的参数&#xff0c; 完成对应的操作。 下面的例子就是一个函数 int plus(int n) {return n; }上面的代码声明了一个函数plus()。 2.函数声明的语法有以下几点&#xff0c;需要注意。 返回值类型。 函数声明时&a…

每日奇难怪题(持续更新)

1.以下程序输出结果是() int main() {int a 1, b 2, c 2, t;while (a < b < c) {t a;a b;b t;c--;}printf("%d %d %d", a, b, c); } 解析:a1 b2 c2 a<b 成立 ,等于一个真值1 1<2 执行循环体 t被赋值为1 a被赋值2 b赋值1 c-- c变成1 a<b 不成立…

【油猴脚本】00006 案例 Tampermonkey油猴脚本自定义表格列名称,自定义表格表头,自定义表格的thead里的td

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【油…

数据结构一:绪论

&#xff08;一&#xff09;数据结构的基本概念 1.相关名词 【1】数据 1.信息的载体&#xff0c;描述客观事物 2.能被输入到计算机中 3.能被计算机程序识别和处理的符号的集合。 【2】数据元素 1.数据的一个“个体” 2.数据的基本单位 3.有时候也被称为元素、结点、顶点…

【STM32】外部中断

当程序正常运行执行main函数&#xff0c;此时如果外部中断来了&#xff0c;执行外部中断函数&#xff0c;实现相应的功能&#xff0c;然后就可以回到main. 一般stm32芯片每个引脚都有自己的外部中断&#xff0c;但是为了限制&#xff0c;会有一个中断线&#xff0c;对应一个中断…

前端Excel热成像数据展示及插值算法

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏:《 VUE 》 《 javaScript 》 &#x1f4dd; 个人网站 :《 江城开朗的豌豆&#x1fadb; 》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 目录 &#x1f4d8; 前言 &#x1f4d8;一、热成像数…

服务器数据增量迁移方案-—SAAS本地化及未来之窗行业应用跨平台架构

一、数据迁移增量同步具有以下几个优点&#xff1a; 1. 减少数据传输量&#xff1a;只传输自上次同步以来更改的数据&#xff0c;而不是整个数据集&#xff0c;这显著降低了网络带宽的使用和传输时间。 2. 提高同步效率&#xff1a;由于处理的数据量较小&#xff0c;同步过程…

MyBatis中Collection和Association的底层实现原理

MyBatis中Collection和Association的底层实现原理 Hi &#x1f44b;, Im shy 有人见尘埃&#xff0c;有人见星辰 技术咨询 引言 在 MyBatis 中&#xff0c;<collection> 和 <association> 标签用于处理一对多和一对一的关系。这两个标签在底层通过缓存、对象创…