当前位置: 首页 > article >正文

C++ 多态原理

用一个题目引入:

现有代码:

class Base
{
public:
	virtual void func()
	{
		cout << "Base:func()" << endl;
	}
protected:
	int _a=1;
	char _b='x';
};
void test1()
{
	Base obj;
	cout << sizeof(obj) << endl;
}

32位平台上输出的是12,按照我们之前学习的内容可知,类的大小实际上按照对齐规则计算类中成员变量的大小,那么这里应该是8;

输出12的原因:类含有虚函数,那么会类中会有一个指针(_vfptr),这个指针是一个指向函数指针数组的指针,也叫虚函数表指针或者虚表指针;指针的大小是4,指针指向一个数组,数组里面存放的是虚函数的地址,这个数组至少存放一个虚函数的地址;

这个指针不一定放在成员变量前面,这个根据平台而定;

一、多态是如何实现的?

在满足多态条件之后,程序运行到指向的对象的虚表中找到对应函数的地址进行调用;

父类有父类的虚表,子类有子类的虚表;

###代码演示:

class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-打折" << endl; }
};
class Soldier : public Person {
public:
	virtual void BuyTicket() { cout << "买票-优先" << endl; }
};
void Func(Person* ptr)
{
	// 这⾥可以看到虽然都是Person指针Ptr在调⽤BuyTicket
	// 但是跟ptr没关系,⽽是由ptr指向的对象决定的。
	ptr->BuyTicket();
}
void test2()
{
	Person p1;
	Student st1;
	Soldier so1;
	Func(&p1);
	Func(&st1);
	Func(&so1);
}

 

二、虚表详析

1、对于基类,基类中的虚表存放基类所有虚函数的地址;

2、对于派生类,首先它继承基类中的虚函数表,两个虚函数表指针值不同,这个指针也指向基类中的那份虚函数表,那么派生类虚函数表中就存放有基类虚函数表中相同的函数地址,若是对其中某些虚函数进行了重写,那么派生类虚表的虚函数的地址就会改变;最后派生类虚表还有派生类自己的虚函数的地址;

###代码示例:

class A
{
public:
	virtual void func1()
	{
		cout << "A::func1()" << endl;
	}
	virtual void func2()
	{
		cout << "A::func2()" << endl;
	}
	void func3()
	{
		cout << "A::func3()" << endl;
	}
};
class B :public A
{
public:

};
class C :public A
{
public:
	virtual void func1()
	{
		cout << "C::func1()" << endl;
	}
	virtual void func2()
	{
		cout << "C::func2()" << endl;
	}
	virtual void func3()
	{
		cout << "C::func3()" << endl;
	}
};
void test3()
{
	A a1;
	B b1;
	C c1;
}

 

首先A是一个基类,那么它的虚函数表中有两个虚函数;然后是b1,B类中没有内容,但是它继承A,那么就继承了A中的虚表,但是两个虚表指针不同(也就是_vfptr),但是指向的虚表相同,所以a1和b1虚表中函数地址都相同;C也继承了A,但是C中对虚函数进行了重写,所以虚表中的函数地址也不同了,这里的c1 func3是要显示的,这里出错了。

3、虚函数和普通函数一样存放到代码段,虚函数表中存的只是这些虚函数的地址;

4、在vs下,虚函数表存放到代码段(常量区)

###代码示例:

class Base {
public:
	virtual void func1() { cout << "Base::func1" << endl; }
	virtual void func2() { cout << "Base::func2" << endl; }
	void func5() { cout << "Base::func5" << endl; }
protected:
	int a = 1;
};
class Derive : public Base
{
public:
	// 重写基类的func1
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func1" << endl; }
	void func4() { cout << "Derive::func4" << endl; }
protected:
	int b = 2;
};
void test4()
{
	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("Person虚表地址:%p\n", *(int*)p3);
	printf("Student虚表地址:%p\n", *(int*)p4);
	printf("虚函数地址:%p\n", &Base::func1);
	printf("普通函数地址:%p\n", &Base::func5);
}

虚表的地址和常量区的最近,所以vs下虚表是在常量区的;

三、动态绑定和静态绑定

1、静态绑定就是在对不满足多态条件的函数调用在编译时绑定,在编译时调用函数的地址;

2、动态绑定就是对满足多态条件的函数在运行时通过虚表调用函数的地址;


http://www.kler.cn/a/378418.html

相关文章:

  • Qt Designer客户端安装和插件集(pyqt5和pyside2)
  • w014基于Springboot校园管理系统的设计与实现
  • C++线程的移动语义
  • 一些硬件知识【2024/11/3】
  • koa + sequelize做距离计算(MySql篇)
  • 双因子认证(Two-factor authentication)简介
  • 提升教育质量:SpringBoot在线试题库系统
  • 微服务保护相关面试题
  • OpenAI官方提供撰写提示词最佳实践
  • Python3 No module named ‘pymysql‘
  • 【解决方案】微信小程序如何使用 ProtoBuf 进行 WebSocket 通信
  • 0,国产FPGA(紫光同创)-新建PDS工程
  • Java爬虫:在1688上“照片快递”上传图片
  • 【P2-3】ESP8266 WIFI模块在STA模式下作为TCP服务器与多个电脑/手机网络助手(TCP客户端)通信——TCP数据透传
  • 贪心算法(Greedy Algorithm)
  • 计算机毕业设计——ssm基于SSM框架的华建汽车出租系统设计与实现演示录像2021
  • 线性代数:Matrix2x2和Matrix3x3
  • FFmpeg 4.3 音视频-多路H265监控录放C++开发八,使用SDLVSQT显示yuv文件 ,使用ffmpeg的AVFrame
  • 大模型的常用指令格式 --> ShareGPT 和 Alpaca (以 llama-factory 里的设置为例)
  • 力扣(leetcode)每日一题 3259 超级饮料的最大强化能量|动态规划
  • 偏差与方差的基本概念
  • guit fok 更新代码
  • 使用 OpenCV 进行人脸检测
  • 基于Spring Boot+Vue的助农销售平台(协同过滤算法、限流算法、支付宝沙盒支付、实时聊天、图形化分析)
  • 【云原生】Docker搭建开源翻译组件Deepl使用详解
  • k8s-实战——ES集群部署