多态(Vptr指针与虚函数表)

本文深入探讨了C++中虚函数的概念及其如何实现多态。通过具体代码实例,详细解析了虚函数表的工作原理,包括虚函数指针的初始化、函数重写及多态调用过程。同时,讨论了虚函数在基类与派生类中的应用,以及如何通过虚继承解决同名成员的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 本笔记主要记录运行时多态——虚函数实现。如下,代码可以先略过直接看下面结论。

多态:根据实际的对象类型决定函数调用语句的具体调用目标

typedef void(*VFUNC)(void);
/*
typedef void (*)()     VE;
int   arr[i]     int []*/
void PrintTable(VFUNC table[])//VFUNC * table,数组里面都是指针,以0结束
{
	cout << "虚表指针" << table << endl;
	for (size_t i = 0; table[i] != nullptr; ++i)
	{
		cout << "vatable" << i<<" :   "<< table[i];
		VFUNC vf = table[i];
		vf();

	}
}
//多态使用虚函数表来实现
//虚函数在编译阶段生成,存储在静态区,推荐把基类的析构函数定义为虚函数,
//虚函数表指针是在构造函数初始化列表阶段初始化的

class Base1
{
public:
	virtual void func1() { cout << "我是base1函数1" << endl; }
	virtual void func2() { cout << "我是base1函数2" << endl; }
	int a;

};

class Base2
{
public:
	virtual void func1() { cout << "我是base2函数1" << endl; }
	virtual void func2() { cout << "我是base2函数2" << endl; }
	int a;

};

class Base3
{
public:
	virtual void func1() { cout << "我是base2函数1" << endl; }
	virtual void func2() { cout << "我是base2函数2" << endl; }
	int aaa;

};

class Derive : public Base2,public Base1,public Base3
{
public:
	virtual void func1() { cout << "我是派生类函数1" << endl; }
	virtual void func3() { cout << "我是派生类函数3" << endl; }
	virtual void func4(){}
	int a;
};
int main()
{
	Base1 b;
	PrintTable((VFUNC*)(*(int*)&b));//取对象b的头四个字节。即强转为int* 。再解引用,然后强转为函数参数类型VFUNC*  
	cout << "_________________________________________" << endl;
	Derive D;
	PrintTable((VFUNC*)(*(int*)&D));
	PrintTable
	(
		(VFUNC*)
		(*(int*)
		(
			(char*)
			&D+sizeof(Base2)
			)
			)
	); //取对象D的头四个字节。即强转为int* 。再解引用,然后强转为函数参数类型VFUNC*,但要加上偏移量才能取到

	cout << "_________________________________________" << endl;

	D.Base1::a = 1;
	D.Base2::a = 0;
	cout << D.Base1::a <<"  "<< D.Base2::a<< endl;

	D.a = 2;
	cout << D.a << endl;

	Base3 b3;
	cout <<"派生类大小:"<<sizeof(D)<<endl <<"基类大小:"<<sizeof(b)<<sizeof(b3)<< endl;
//在此处打断点
}

打印结果如图,利用监控内存得到3个内存图

内存图1

内存图2

内存图3

(另外,我将base3类中的打印信息改成    virtual void func2() { cout << "我是base3函数2" << endl; }依然会打印“我是base2函数2”,这是因为D是先继承base2函数的,同名函数使用的是先继承的

对以上监视窗口和控制台打印信息,我们很容易得出虚函数在多态中的性质。以及虚函数是如何实现多态的。

1,一个有趣现象,基类和派生类中都有int a,通过作用域赋值,可以发现同名变量可以不同赋值,但这样是不好的写法,应该使用虚继承。如图,基类的a并没有初始化,我们初始化的只是D

2,基类base1对象b为8个字节,包含一个虚表指针和一个int,虚表指针存储的地址指向一段空间(虚函数表vtable),虚函数表存储2个base1函数指针,利用函数指针调用函数确定为其基类函数。(看内存图1)

派生类Derive对象D为28字节(三个基类的int外加自己的int 是4x4=16字节,剩余12个字节是3个基类函数指针vptr)。既然有3个基类指针指向3个虚函数表,我们就可以得到3张表,不过为了方便,我们就拿前2张表举例子,第一张虚基类表的在类的前四个字节,第二张表在[5,8]字节处。(利用指针我们取得了2张表,原理在代码注释中给出)内存图2和内存图3都是派生类虚函数表。有了这些虚表,我们在使用中,就可以依据不同的条件使用不同函数。

Base1有基类函数fun1,fun2;Base2也有基类函数fun1,fun2;(Base3可以忽略,是我用来验证类大小和基类大小关系的),

我们的派生类Derive 重写虚函数(Override virtual function)fun1,并添加自己的fun3,fun4。

Base1的fun1,Base2的fun1,自己的fun1 三个同名函数三个函数内容可以不一样。

 

同时继承Base1的虚表,和Base2的虚表上有对应的fun2,虽然Derive没有实现fun2,但是也可以多态调用基类的fun2。Derive自己重写虚函数fun1,虚函数表中就改为派生类fun1(不影响基类的虚表,二者存储空间不同,是2张表)。

实际中的多态一般是一个基类,多个派生类,比如基类定义游乐园买票,设置为纯虚函数(virtual void BuyTicket()=0),强制派生类实现,一个派生类实现为买水上乐园票(限白天),一个派生类设置为买陆上区晚场票,一个派生类设置为陆上区早场票。实现了一个接口多个实现。用父类的指针去指向子类。

//上面基础上添加一些代码
class Derive2 :public Base1
{
public:
	virtual void func1() { cout << "我是第二个派生类函数1" << endl; }
};

void test_duotai(Base1 &base)
{
	base.func1();
}
//现在是Derive2继承Base1,并重写fun1

//下面这些是主函数中的测试,不同方式访问基类和派生类的func1,1111111下面的就是多态的表现,当然基类设置为纯虚函数更加符合设计
    D.func1();
	D.Base1::func1();
	
	Base1* pb=&D;
	pb->func1();
	b.func1();
	Base1* pb2 = new Derive();
	Base1* pb3 = new Derive2();
	
	pb2->func1();
	pb3->func1();
	cout << "1111111111111111111111111" << endl;
	test_duotai(b);
	test_duotai(D);
	Derive2 dd;
	test_duotai( dd);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值