C++中的多态(原理篇)

本文详细探讨了C++中的虚函数表和多态原理。通过实例代码展示了虚函数表如何存储函数指针,以及在单继承和多继承情况下的表现。动态绑定(后期绑定)与静态绑定(前期绑定)的概念也被清晰解释,揭示了C++中动态多态的实现方式。此外,还介绍了如何通过调试工具查看和理解虚函数表的内容。

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

多态的原理

虚函数表

下面这一串代码

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


我们看到了a对象的大小是8bit大小,但是a对象里面不应该只是一个_a吗?当我们打开监视窗口发现在这里插入图片描述
a对象中不仅存储了一个成员_a,还有一个_vfptr放在成员_a的前面,对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。这个指针指向虚函数表,因为虚函数的地址要放在虚函数表中,由此可以直接找到虚函数的位置。
我们将上面的代码改写一下,让B去继承A类

class A
{
public:
	virtual void func1()
	{
		cout << "func1()" << endl;
	}
	virtual void func2()
	{
		cout << "func2()" << endl;
	}
	void func3()
	{
		cout << "func3()" << endl;
	}
private:
	int _a;
};
class B :public A
{
public:
	virtual void func1()
	{
		cout << "func1()" << endl;
	}
private:
	int _b;
};
int main()
{
	A a;
	B b;
	return 0;
}

调试之后
在这里插入图片描述
经过调试之后我们发现,在 子类对象b中也有一个虚表指针,并且b对象将a对象中的虚表继承了下来,并且对第一个func1函数进行了重写,这里直接将func1函数进行了覆盖,因此重写也被称作覆盖。对于func2()是继承下来的虚函数,对于func3(),因为其不是虚函数,那么也自然没有放在虚表之中。
虚函数表的本质是一个存放函数指针的数组,在这个数组中一般情况下在最后一个位置为空(nullptr)(VS下)。

动态绑定与静态绑定

  1. 静态绑定称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载
  2. 动态绑定称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。

单继承的虚表函数

class A
{
public:
	virtual void func1()
	{
		cout << "func1()" << endl;
	}
	virtual void func2()
	{
		cout << "func2()" << endl;
	}
	virtual void func3()
	{
		cout << "func3()" << endl;
	}
private:
	int _a;
};
class B :public A
{
public:
	virtual void func1()
	{
		cout << "func1()" << endl;
	}
	virtual void func4()
	{
		cout << "func4()" << endl;
	}
	virtual void func5()
	{
		cout << "func5()" << endl;
	}
private:
	int _b;
};
int main()
{
	A a;
	B b;
	return 0;
}

上面的代码中,我们通过调试发现

在继承之后,重写了func1()函数,但是并没有看见func4()和func5()函数,这里是监视窗口的一个问题,将其隐藏了,我们使用代码打印,可以直接打印出来虚函数表里面的内容。

typedef void(*VF_PTR)();

void PrentfVfptr(VF_PTR* table)
{
	for (int i = 0; table[i] != nullptr; i++)
	{
		printf("第%d个虚函数的地址->%p\n", i, table[i]);
	}
	cout << endl << endl;
}

我们这个函数可以直接打印出虚函数表中的地址,因为我们虚函数表中存储的是函数指针,我们将函数指针typedef之后,虚函数表的本质是一个数组,因此我们可以使用数组的方式来进行打印。
在这里插入图片描述

多继承中的虚函数表

class A
{
public:
	virtual void func1() { cout << "func1()" << endl; }
	virtual void func2() { cout << "func2()" << endl; }
private:
	int _a;
};
class B
{
public:
	virtual void func1() { cout << "func1()" << endl; }
	virtual void func2() { cout << "func2()" << endl; }
private:
	int _b;
};
class C :public A, public B
{
public:
	virtual void func1() { cout << "func1()" << endl; }
	virtual void func2() { cout << "func2()" << endl; }
private:
	int _c;
};

typedef void(*VF_PTR)();

void PrentfVfptr(VF_PTR* table)
{
	for (int i = 0; table[i] != nullptr; i++)
	{
		printf("第%d个虚函数的地址->%p\n", i, table[i]);
	}
	cout << endl << endl;
}
int main()
{
	A a;
	B b;
	C c;
	PrentfVfptr((VF_PTR*)*((void**)&c));
	return 0;
}

上面一串代码,当我们打印C对象的虚函数表的时候,我们发现只打印出来了两个地址
在这里插入图片描述
明明有4个虚函数被继承了下来了在这里插入图片描述
这里我们看到了应该有两份虚函数表这里我们应该调用两次打印函数,那么第个虚函数表的位置在哪里呢,应该是c对象的开始往后偏移sizeof(a)的位置,那么此时就可以

int main()
{
	A a;
	B b;
	C c;
	PrentfVfptr((VF_PTR*)*((void**)&c));
	PrentfVfptr((VF_PTR*)*((void**)(((char*)&c+sizeof(a)))));
	return 0;
}

在这里插入图片描述
这样我们就能打印出来完整的虚函数表了
还有,我们在继承中学习了切片,那么这里我们也可以使用切片去实现

int main()
{
	A a;
	B b;
	C c;
	PrentfVfptr((VF_PTR*)*((void**)&c));

	B* p = &c;
	PrentfVfptr((VF_PTR*)*((void**)(p)));

	PrentfVfptr((VF_PTR*)*((void**)(((char*)&c+sizeof(a)))));
	return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值