【C++】多态功能细节问题分析

news2025/5/10 14:24:09

        多态是在不同继承关系的类对象去调用同一函数,产生了不同的行为。值得注意的是,虽然多态在功能上与隐藏是类似的,但是还是有较大区别的,本文也会进行多态和隐藏的差异分析。

  1. 在继承中要构成多态的条件

                 1.1必须通过基类的指针或引用调用虚函数。

                1.2被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写。

以上两个条件就是判断多态的充分必要条件,在笔试中该类问题考察比较多,需要值得注意。

什么是虚函数?——可以简单的理解在成员函数前加一个virtual就变成虚函数了,至于虚函数与普通函数有什么区别就是动态绑定和静态绑定的区别,下面会仔细介绍。

而虚函数的重写也具备条件:虚函数+三同(同名、同返回值、同形参)。

下面是一个多态与非多态的比较:

#include<iostream>

using namespace std;

class Person
{
public:
	
	Person(size_t val ):TicketPrice(val){}
	//Person(size_t val = 100) :TicketPrice(val) {}

	virtual void GetPrice()
	{
		cout << "Person's TicketPrice : " << TicketPrice << endl;
	}

protected:
	size_t TicketPrice;
};

class Man:public Person
{
public:
	Man(size_t val) :Person(val){}

	virtual void GetPrice() override
	{
		cout << "Man's TicketPrice : " << TicketPrice << endl;
	}


};

class Child :public Person
{
public:
	Child(size_t val) :Person(val) { TicketPrice /= 2; }
	virtual void GetPrice() override
	{
		cout << "Child's TicketPrice : " << TicketPrice << endl;
	}


};


int main()
{
	Child cd(800);
	cd.GetPrice();//非多态
	//cd.Person::GetPrice();

	Person* pp;
	pp = new Child(800);//多态
	pp->GetPrice();
	return 0;
}

为什么pp->GetPrice()会输出Child’s TicketPrice : 400 呢?这就是多态的动态绑定的效果。

动态绑定 vs 静态绑定

普通函数:采用静态绑定(编译时绑定),即函数调用是在编译时确定的。这意味着函数调用的目标地址在编译阶段就已经固定下来了。

虚函数:采用动态绑定(运行时绑定),即函数调用是在运行时根据对象的实际类型来决定的。这使得程序可以根据对象的实际类型调用相应的函数版本。

根据动态绑定的特性,即运行时调用,结合重写的特性,其实就不难理解多态的原理了。

2、特殊的多态

在多态中有一个特殊的多态,即析构函数的多态。

首先,我们先测试一下非虚析构函数和虚析构函数的差别:

非虚析构函数:

class Base
{
public:
	~Base() { cout << "Base Destruct!" << endl; }

};

class Derived:public Base
{
public:
	~Derived() { cout << "Dericed Destruct!" << endl; }

};

int main()
{
	Base* bp;
	bp = new Derived();
	delete bp;
	return 0;
}

如果基类的析构函数不是虚函数,在通过基类指针删除派生类对象时,只会调用基类的析构函数,而不会调用派生类的析构函数。这可能会导致派生类中的资源没有被正确释放,从而造成内存或其他资源泄漏。

虚析构函数:

class Base
{
public:
	virtual ~Base() { cout << "Base Destruct!" << endl; }

};

class Derived:public Base
{
public:
	virtual ~Derived() override { cout << "Dericed Destruct!" << endl; }

};

int main()
{
	Base* bp;
	bp = new Derived();
	delete bp;
	return 0;
}

如果基类的析构函数被声明为虚函数,那么通过基类指针删除派生类对象时,首先会调用派生类的析构函数,然后是基类的析构函数。这样可以确保所有层级的对象都被正确销毁。

***提示:虚构函数编译器会自动识别为~Destructor(),所以即使基类和派生类的虚构函数名不同也会构成重写(不信你可以使用override测试一下)。

3、虚表

虚表(简称vtable)是C++实现动态绑定和多态的一种机制。当一个类包含至少一个虚函数时,编译器通常会为该类生成一个虚表,并在每一个对象中添加一个指向这个虚表的指针。

在C++中,当派生类对象赋值给基类对象时,不会拷贝虚表指针或虚表本身,这个赋值操作只会复制基类部分的数据成员,而不会涉及派生类特有的数据成员。上述现象的一个重要原因是对象切片(Object Slicing)。当一个派生类对象赋值给基类对象时,只有基类部分的数据会被复制,而派生类特有的部分会被“切掉”。

class Base
{
public:
	Base(int val=1) :_b(val) {}
	virtual void func1() {
		cout << "This is Base func1" << endl;
	}
	virtual void func2() {
		cout << "This is Base func2" << endl;
	}
protected:
	int _b;
};

class Derived:public Base
{
public:
	Derived():_d(2){}
	virtual void func1() {
		cout << "This is Derived func1" << endl;
	}
	virtual void func2() {
		cout << "This is Derived func2" << endl;
	}
	virtual void func3() {
		cout << "This is Derived func3" << endl;
	}
protected:
	int _d;
};

int main()
{
	Base b(2);
	Derived d;
	b = d;
	b.func1();
	d.func1();
	return 0;
}

当然,如果你需要保留派生类的特性,可以使用指针或引用来避免对象切片。

class Base
{
public:
	Base(int val=1) :_b(val) {}
	virtual void func1() {
		cout << "This is Base func1" << endl;
	}
	virtual void func2() {
		cout << "This is Base func2" << endl;
	}
protected:
	int _b;
};

class Derived:public Base
{
public:
	Derived():_d(2){}
	virtual void func1() {
		cout << "This is Derived func1" << endl;
	}
	virtual void func2() {
		cout << "This is Derived func2" << endl;
	}
	virtual void func3() {
		cout << "This is Derived func3" << endl;
	}
protected:
	int _d;
};

int main()
{
	Base* b = new Derived();
	b->func1();
	return 0;
}


值得注意的是,虚表的存贮位置是值得讨论的,很多人博客的表述及通义千问都是认为虚表是存储在静态区,但是从实际操作来看似乎有些问题。

那我们开始测试看虚表是否存储在静态区中吧!

class Base
{
public:
	Base(int val=1) :_b(val) {}
	virtual void func1() {
		cout << "This is Base func1" << endl;
	}
	virtual void func2() {
		cout << "This is Base func2" << endl;
	}
protected:
	int _b;
	const static int x = 1;
};

class Derived:public Base
{
public:
	Derived():_d(2){}
	virtual void func1() {
		cout << "This is Derived func1" << endl;
	}
	virtual void func2() {
		cout << "This is Derived func2" << endl;
	}
	virtual void func3() {
		cout << "This is Derived func3" << endl;
	}
protected:
	int _d;
};

typedef void(*FUNC_PTR)();

int main()
{
 	int a = 0;
	printf("栈区:%11p\n", &a);
	int* ap = new int();
	printf("堆区:%11p\n", ap);
	static int as = 1;
	printf("静态区: %p\n", &as);
	const char* s = "hello";
	printf("常量区: %p\n", s);

	Base b;
	printf("虚表:%11p\n", *((int*)&b));
	//int tmp = ;
	FUNC_PTR f = *((FUNC_PTR*)(*((int*)&b)));//测试是不是虚表项地址
	f();

	return 0;
}

每一个区都是以块的组织方式进行存贮,所以我们只需要比较虚表的地址与a\ap\as\s哪个变量的地址更加靠经就基本能判定b的虚表存储在哪个区。由上图可知,虚表地址更加靠近常量区,所以得出结论——虚表存储在常量区。

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

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

相关文章

EIP-712:类型化结构化数据的哈希与签名

1. 引言 以太坊 EIP-712: 类型化结构化数据的哈希与签名&#xff0c;是一种用于对类型化结构化数据&#xff08;而不仅仅是字节串&#xff09;进行哈希和签名 的标准。 其包括&#xff1a; 编码函数正确性的理论框架&#xff0c;类似于 Solidity 结构体并兼容的结构化数据规…

基于S函数的simulink仿真

基于S函数的simulink仿真 S函数可以用计算机语言来描述动态系统。在控制系统设计中&#xff0c;S函数可以用来描述控制算法、自适应算法和模型动力学方程。 S函数中使用文本方式输入公式和方程&#xff0c;适合复杂动态系统的数学描述&#xff0c;并且在仿真过程中可以对仿真…

每日一题洛谷P8664 [蓝桥杯 2018 省 A] 付账问题c++

P8664 [蓝桥杯 2018 省 A] 付账问题 - 洛谷 (luogu.com.cn) 思路&#xff1a;要使方差小&#xff0c;那么钱不能一下付的太多&#xff0c;可以让钱少的全付玩&#xff0c;剩下还需要的钱再让钱多的付&#xff08;把钱少的补上&#xff09;。 将钱排序&#xff0c;遍历一遍&…

迅饶科技X2Modbus网关-GetUser信息泄露漏洞

免责声明&#xff1a;本号提供的网络安全信息仅供参考&#xff0c;不构成专业建议。作者不对任何由于使用本文信息而导致的直接或间接损害承担责任。如涉及侵权&#xff0c;请及时与我联系&#xff0c;我将尽快处理并删除相关内容。 漏洞描述 该漏洞的存在是由于GetUser接口在…

关于inode,dentry结合软链接及硬链接的实验

一、背景 在之前的博客 缺页异常导致的iowait打印出相关文件的绝对路径-CSDN博客 里 2.2.3 一节里&#xff0c;我们讲到了file&#xff0c;fd&#xff0c;inode&#xff0c;dentry&#xff0c;super_block这几个概念&#xff0c;在这篇博客里&#xff0c;我们针对inode和dentr…

PandasAI:当数据分析遇上自然语言处理

数据科学的新范式 在数据爆炸的时代&#xff0c;传统的数据分析工具正面临着前所未有的挑战。数据科学家们常常需要花费70%的时间在数据清洗和探索上&#xff0c;而真正的价值创造时间却被大幅压缩。PandasAI的出现&#xff0c;正在改变这一现状——它将生成式AI的强大能力注入…

Unity网络开发基础 (3) Socket入门 TCP同步连接 与 简单封装练习

本文章不作任何商业用途 仅作学习与交流 教程来自Unity唐老狮 关于练习题部分是我观看教程之后自己实现 所以和老师写法可能不太一样 唐老师说掌握其基本思路即可,因为前端程序一般不需要去写后端逻辑 1.认识Socket的重要API Socket是什么 Socket&#xff08;套接字&#xff0…

做题记录:和为K的子数组

来自leetcode 560 前言 自己只会暴力&#xff0c;这里就是记录一下前缀和哈希表的做法&#xff0c;来自灵神的前缀和哈希表&#xff1a;从两次遍历到一次遍历&#xff0c;附变形题 正文 首先&#xff0c;这道题无法使用滑动窗口&#xff0c;因为滑动窗口需要满足单调性&am…

VMware虚拟机卡顿、CPU利用率低、编译Linux内核慢,问题解决与实验对比

目录 一、总结在前面&#xff08;节约时间就只看这里&#xff09;0 环境说明1 遇到的问题&#xff1a;2 问题的原因&#xff1a;3 解决办法&#xff1a;4 实验验证&#xff1a;5 关于虚拟机内核数量设置6 关于强行指定Vm能用的CPU内核 二、管理员启动&#xff0c;实验对比实验1…

【7】数据结构的队列篇章

目录标题 队列的定义顺序队列的实现初始化入队出队顺序队列总代码与调试 循环队列的实现初始化入队出队获取队首元素循环队列总代码与调试 链式队列的实现链式队列的初始化入队出队获取队首元素链式队列总代码与调试 队列的定义 定义&#xff1a;队列&#xff08;Queue&#x…

颜色归一化操作

当我们不太关注图像具体细节&#xff0c;只关注图像大致的内容时&#xff0c;为了避免光照角度、光照强度对图像的影响&#xff0c;可以采用下面进行归一化操作。这种颜色系统具有通道对表面方向、照明方向具有鲁棒性的特性&#xff0c;适用于图像分割等领域&#xff0c;在机器…

深度学习处理文本(6)

理解词嵌入 重要的是&#xff0c;进行one-hot编码时&#xff0c;你做了一个与特征工程有关的决策。你向模型中注入了有关特征空间结构的基本假设。这个假设是&#xff1a;你所编码的不同词元之间是相互独立的。事实上&#xff0c;one-hot向量之间都是相互正交的。对于单词而言…

STL-vector的使用

1.STL-vector 向量是可以改变其大小的线性序列容器。向量使用连续的空间存储元素&#xff0c;表明向量可以像数组通过下标来访问元素&#xff0c;但是向量的大小可以动态变化。向量的容量可能大于其元素需要的实际容量&#xff0c;向量通过消耗更多的内存来换取存储管理效率。…

MySQL深入

体系结构 连接层&#xff1a;主要处理客户端的连接进行授权认证、校验权限等相关操作 服务层&#xff1a;如sql的接口、解析、优化在这里完成&#xff0c;所有跨存储引擎的操作在这里完成 引擎层&#xff1a;索引是在存储引擎层实现的&#xff0c;所以不同的存储引擎他的索引…

Genspark:重新定义搜索体验的AI智能体引擎

关于我们 飞书-华彬智融知识库 由前百度高管景鲲&#xff08;Eric Jing&#xff09;和朱凯华&#xff08;Kay Zhu&#xff09;联合创立的AI搜索引擎Genspark&#xff0c;正以革命性的技术架构和用户导向的设计理念&#xff0c;为全球用户带来一场搜索体验的范式革命。本文将基…

从零实现Json-Rpc框架】- 项目实现 - 服务端主题实现及整体封装

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…

AI助力PPT制作,让演示变得轻松高效

AI助力PPT制作&#xff0c;让演示变得轻松高效&#xff01;随着科技的进步&#xff0c;AI技术早已渗透到各行各业&#xff0c;特别是在办公领域&#xff0c;AI制作PPT已不再是未来的梦想&#xff0c;而是现实的工具。以前你可能需要花费数小时来制作一个完美的PPT&#xff0c;如…

React-01React创建第一个项目(npm install -g create-react-app)

1. React特点 JSX是javaScript语法的扩展&#xff0c;React开发不一定使用JSX。单向响应的数据流&#xff0c;React实现单向数据流&#xff0c;减少重复代码&#xff0c;比传统数据绑定更简单。等等 JSX是js的语法扩展&#xff0c;允许在js中编写类似HTML的代码 const …

C++学习笔记之内存管理

仅用于记录学习理解 选择题答案及解析 globalVar&#xff1a;C&#xff08;数据段 (静态区)&#xff09; 解析&#xff1a;全局变量存放在数据段&#xff08;静态区&#xff09;&#xff0c;生命周期从程序开始到结束&#xff0c;程序运行期间一直存在。 staticGlobalVar&…

【MyBatis】深入解析 MyBatis XML 开发:增删改查操作和方法命名规范、@Param 重命名参数、XML 返回自增主键方法

增删改查操作 接下来&#xff0c;我们来实现一下用户的增加、删除和修改的操作。 增( Insert ) UserInfoMapper接口&#xff1a; 我们写好UserInfoMapper接口后&#xff0c;自动生成 XML 代码&#xff1b; UserInfoMapper.xml实现&#xff1a; 增删改查方法命名规范 如果我们…