多态的原理、单继承和多继承的虚函数表、以及虚函数表的打印。

news2024/12/24 3:46:36

一、多态原理

1、下面这个结果是多少?

class A
{
public:
	virtual void func()
	{
		cout << "func()" << endl;
	}

private:
	int _a = 1;
};

int main()
{
	printf("%d\n", sizeof(A));
	return 0;
}

是 4?8?还是多少?打印结果如下:

 

为什么是 8 呢?,通过调试看一下,如下:

 通过调试可以看到里面不只是有 _a 成员,还多了一个成员 __vfptr,这个成员就是虚函数表指针,为什么会有虚函数表指针呢?因为虚函数的地址要存放到虚函数表中!并且含有虚函数的类中至少有一个虚函数表指针!

2. 上面的代码修改一下,如下

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

	virtual void Func2()
	{
		cout << "A::Func2()" << endl;
	}

	void Func3()
	{
		cout << "A::Func3()" << endl;
	}
private:
	int _a = 1;
};

class B : public A
{
public:
	virtual void Func1()
	{
		cout << "B::Func1()" << endl;
	}
private:
	int _b = 2;
};

int main()
{
	A a;
	B b;
	return 0;
}

调试看一下内部结构是什么,如下:

  

通过上面的调试可以看到:

(1) 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。

(2) 基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。

(3)另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会放进虚表。

(4)虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。

(5)总结一下派生类的虚表生成:

        a.先将基类中的虚表内容拷贝一份到派生类虚表中 

        b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 

        c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。

(6)那么虚函数存在哪的?虚表存在哪的? 
        虚函数存在虚表?虚表存在对象中?
        不对!!!虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。那么虚表存在哪的呢?实际我们去验证一下会发现vs下是存在代码段的。

通过上面的例子,下面正式看一下多态原理!!

上一篇文章讲了多态的条件,满足多态条件的时候,传给父类指针或者引用的时候,会完成不一样的调用,那么下面解释一下为什么,如下:

class Person {
public:
	virtual void BuyTicket() { cout << "买票全价" << endl; }
};
 
class Student : public Person
{
public:
	virtual void BuyTicket() { cout << "买票半价" << endl; }
};
 
void Func(Person& p)
{
	p.BuyTicket();
}
 
int main()
{
	Person q;
	Func(q);

	Student st;
	Func(st);
 
	return 0;
}

因为 q 传给 p 引用的时候,p 是指向 q 对象的,所以发生了切片,但是又因为都是Person这个类的,所以调用了自己的方法。

st 传给 p 引用的时候,p 是指向 st 对象的,student 又继承了Person类,并且重写了虚函数,形成了多态,但是 st 对象里面的虚函数是重写了之后的!而不是继承下来的虚函数!可以调试查看一下,如下:

虽然发生切片, p 指向 st 对象中 Person 前面的地址,但是因为是方法是重写的,所以调用的是student 的方法!

下面汇编代码看一下,如下:

 多态的时候 call 的时候,call 的是寄存器!具体细节不展开讲了。

总之多态调用的时候,运行时去指向对象的虚表中找虚函数地址,进行调用!

那么不构成多态是什么呢?

class Person {
public:
	virtual void BuyTicket() { cout << "买票全价" << endl; }
};

class Student : public Person
{
public:
	//virtual void BuyTicket() { cout << "买票半价" << endl; }
	virtual void Buy() { cout << "买票半价" << endl; }
};

void Func(Person& p)
{
	p.BuyTicket();
}

int main()
{
	Person q;
	Func(q);

	Student st;
	Func(st);

	return 0;
}

我随便破坏一个条件已经不构成多态,那么调用的时候是什么样呢? 如下:

可以看到 call 的直接是函数地址! 

因为不满足多态的话就是普通函数调用,编译链接时候,就确认了函数地址,运行时直接调用。

二、单继承和多继承的虚函数表。

// 单继承
// 打印虚函数表
class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "Person::BuyTicket()" << endl;
	}

	virtual void func_person()
	{
		cout << "Person::func_person()" << endl;
	}
};

class Student : public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "Student::BuyTicket()" << endl;
	}

	// 监视窗口查看 s 的虚表 里面查看不到 func_student() 函数
	virtual void func_student()
	{
		cout << "Person::func_student()" << endl;
	}
};


typedef void(*VFTABLE)();

void PRINT_VFTABLE(VFTABLE table[], int n)
{
	//for (int i = 0; table[i] != nullptr; i++) // vs 在后面默认给的 0 ; 所以可以用 table[i] != nullptr; linux 需要显示写打印几个
	for (int i = 0; i < n; i++)
	{
		printf("table[%d]->%p::", i, table[i]);

		VFTABLE pf = table[i];
		pf(); // 函数指针+() 调用自己对应的函数
	}

	cout << endl;
}


//int main()
//{
//	Student s;
//
//	Person p;
//
//	//PRINT_VFTABLE((VFTABLE*)*(int*)&s);
//	PRINT_VFTABLE((VFTABLE*)*(int*)&s, 3); // 子类有 3 个 显示写打印 3 个
//	PRINT_VFTABLE((VFTABLE*)*(int*)&p, 2); // 父类有 2 个 显示写打印 2 个
//
//	return 0;
//}



// 多继承
// 打印虚函数表


class Base1
{
public:
	virtual void func1()
	{
		cout << "Base1::func1()" << endl;
	}

	virtual void func_Base1()
	{
		cout << "Base1::func_Base1()" << endl;
	}
};


class Base2
{
public:
	virtual void func1()
	{
		cout << "Base2::func1()" << endl;
	}

	virtual void func_Base2()
	{
		cout << "Base2::func_Base2()" << endl;
	}
};


class Base3 : public Base1, public Base2
{
public:
	virtual void func1()
	{
		cout << "Base3::func1()" << endl;
	}

	virtual void func_Base3()
	{
		cout << "Base3::func_Base3()" << endl;
	}
};

typedef void(*VFTABLE)();

void PRINT_VFTABLE(VFTABLE  table[])
{
	for (int i = 0; table[i] != nullptr; i++)
	{
		printf("table[%d]->%p->", i, table[i]);

		VFTABLE pf = table[i];
		pf();
	}
	cout << endl;
}

int main()
{
	Base3 b3;
	Base1 b1 = b3;
	Base2 b2 = b3;

	PRINT_VFTABLE((VFTABLE*)*(int*)&b1);
	PRINT_VFTABLE((VFTABLE*)*(int*)&b2);
	PRINT_VFTABLE((VFTABLE*)*(int*)&b3);

	return 0;
}

上面写了一个函数打印虚函数表,可以尝试调试打印看一下。 

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

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

相关文章

Python星际生存小游戏开发

项目介绍&#xff1a; 项目名称&#xff1a;python星际生存游戏 编程语言&#xff1a;python 用到关键知识&#xff1a;pygame模块&#xff0c;面向对象思想&#xff0c;python基础等等 实现功能&#xff1a; 1&#xff1a;飞机的运动&#xff0c;发射子弹&#xff0c;飞机…

深入探索Apache Flume:大数据领域的数据采集神器【上进小菜猪大数据系列】

&#x1f4ec;&#x1f4ec;我是上进小菜猪&#xff0c;沈工大软件工程专业&#xff0c;爱好敲代码&#xff0c;持续输出干货&#xff0c;欢迎关注。 引言&#xff1a; 随着大数据技术的快速发展&#xff0c;企业和组织需要从各种来源采集海量数据。数据采集是大数据处理流程中…

DTO、VO、BO、PO、DO理解等相关的转换

1、什么是DTO、VO、BO、PO、DO、POJO pojo(domain/entity)一般定义实体类&#xff0c;实体类又被分为VO、BO、 PO、 DTO、DO&#xff1b;通过各层POJO的使用&#xff0c;有助于提高代码的可读性和可维护性。 2、阿里巴巴Java开发规范 DO(Data Object):此对象与数据库表结构一…

chatgpt赋能python:Python的BeautifulSoup库和find_all()方法

Python的Beautiful Soup库和find_all()方法 在Web爬虫中&#xff0c;我们需要从网页中找到特定的HTML标记或属性&#xff0c;以便提取我们需要的数据。对于Python开发人员而言&#xff0c;Beautiful Soup是最流行的解析HTML和XML的库之一。该库可以让我们轻松地从HTML解析器中…

ECMAScript 6 新特性详解

目录 ECMAScript 6 简介 1、箭头函数 2、类 3、增强对象字面量 4、模板字符串 5、解构赋值 6、默认参数、剩余参数、展开操作符 7、let、const 8、迭代器&#xff08;迭代器&#xff09;、for of 9、Generators&#xff08;生成器&#xff09; 10、Unicode 11、模块…

GPC_APDU_Transport_over_SPI-I2C_v1.0_PublicRelease

GPC_APDU_Transport_over_SPI-I2C_v1.0_PublicRelease.pdf 目录 1 简介 越来越多的设备&#xff0c;如移动设备、可穿戴设备或其他 IoT&#xff08;物联网&#xff09;设备现在正在使用焊接安全元件 (SE)。 这产生了支持 SPI 或 I2C 等物理接口的新需求&#xff0c;以代替以前…

高阶python | 字符串高级功能

不可变的字符串 python中的数据类型分为可变的和不可变的两种&#xff0c;字符串属于不可变的数据类型。 不可变的数据类型可以作为字典的键使用&#xff0c;例如&#xff1a; my_dict {Lily: 68, Harry: 98, Lucy: 79} 不可变类型的另一个优点就是有助于进行内部消化&…

ISO_IEC_7816-3

介绍 ISO/IEC 7816 是一系列标准&#xff0c;规定了集成电路卡和此类卡的使用 互换。 这些卡是用于在外部世界和卡中的集成电路之间协商的信息交换的识别卡。 作为信息交换的结果&#xff0c;卡传递信息&#xff08;计算结果、存储的数据&#xff09;和/或修改其内容&#xff0…

高级第一个月考试题

1.什么是Vue框架&#xff1f; Vue是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是&#xff0c;Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层&#xff0c;并且还便于与第三方库或既有项目整合。另一方面&#xff0c;当与现代化的工具链以及各种支持…

Unity之ShaderGraph节点介绍 Artistic艺术效果

前言 Artistic&#xff08;艺术效果&#xff09;  1、Adjustments&#xff08;图像调节&#xff09;   1) Chennel Mixer&#xff08;通道混合器&#xff09;   2) Contrast&#xff08;对比度&#xff09;   3) Hue&#xff08;色调偏移&#xff09;   4) Invert Co…

layui框架学习(24:弹出层模块_消息框提示框)

弹出层模块layer是Layui的重要模块&#xff0c;layui官网教程中的原话是&#xff1a;“layer 作为 Layui 的代表性组件”。弹出层模块layer的作用主要是在页面中以消息框、弹出框等形式进行信息提醒、信息交互等操作&#xff0c;类似于C/S架构中的MessageBox.Show、自定义窗口的…

AcrelCloud-6000安全用电云平台在某景区的应用

摘要 我国历史文化悠久&#xff0c;拥有相当丰富的历史文化遗产&#xff0c;而古建筑作为文化遗产的重要组成部分&#xff0c;体现着中华民族的生命力和创造力&#xff0c;蕴含了丰富的物质价值和精神价值。根据国家文物局近些年公布的数据&#xff0c;每年都发生十多起文物建筑…

ThingsBoard教程(五二):规则节点解析 AWS SNS Node, AWS SQS Node

AWS SNS Node Since TB Version 2.0 节点将消息发布到AWS SNS(亚马逊简单通知服务)。 配置: 主题ARN模式 - 可以直接设置消息发布的主题名称,也可以使用模式,该模式将使用消息元数据解析为实际的ARN主题名称。 AWS Access Key ID和AWS Secret Access Key是具有编程访问…

chatgpt赋能python:PythonUSBKey:安全可靠的数字身份验证

Python USBKey&#xff1a;安全可靠的数字身份验证 介绍 Python USBKey是一种安全可靠的数字身份验证工具&#xff0c;它基于Python编程语言开发&#xff0c;便于跨平台使用&#xff0c;并支持多种加密算法&#xff0c;使得加密安全性更高。 Python USBKey能够保护您的隐私&…

C++中string的用法

博主简介&#xff1a;Hello大家好呀&#xff0c;我是陈童学&#xff0c;一个与你一样正在慢慢前行的人。 博主主页&#xff1a;陈童学哦 所属专栏&#xff1a;CSTL 前言&#xff1a;Hello各位小伙伴们好&#xff01;欢迎来到本专栏CSTL的学习&#xff0c;本专栏旨在帮助大家了解…

chatgpt赋能python:Pythonthreading:什么是线程及其使用

Python threading&#xff1a;什么是线程及其使用 在计算机科学中&#xff0c;线程是指操作系统能够进行调度和分派的最小单位。在 Python 中&#xff0c;线程允许程序在执行过程中并行完成多个任务。线程是并发编程的核心元素之一&#xff0c;使开发人员能够轻松地编写并行代…

Golang中互斥锁和读写互斥锁

目录 互斥锁 使用互斥锁的示例代码 读写互斥锁 读写互斥锁的示例代码 互斥锁 在Golang中&#xff0c;互斥锁&#xff08;Mutex&#xff09;是一种基本的同步原语&#xff0c;用于实现对共享资源的互斥访问。互斥锁通过在代码中标记临界区来控制对共享资源的访问&#xff0c…

LC-3 机器码编程实验

一、实验目的 分析和理解试验指定的需解决问题。利用LC-3的机器代码设计实现相关程序。通过LC-3仿真器调试和运行相关程序并得到正确的结果。 二、实验内容 利用LC-3的机器代码计算一个16位的字中有多少位是“1”&#xff0c;程序从x3000开始&#xff0c;需计算的字存储在x3…

智能集成式电力电容器在山东某环保材料制造厂中的应用

摘要&#xff1a;分析智能集成式电力电容的工作原理及功能&#xff0c;结合山东环保材料制造厂配电现状&#xff0c;选择经济可靠的方案&#xff0c;智能电容过零投切与低功耗&#xff0c;解决了继电器投切产生涌流的问题&#xff1b;接线简单&#xff0c;扩容方便&#xff0c;…