C++ 虚函数表

介绍虚函数表前先熟悉一下什么是多态,虚函数以及virtual关键字

什么是多态

子类从父类中继承,不仅可以派生新的接口,还可以对父类的接口进行重写,每个子类都可以对父类的这个接口进行重写,可以通过父类指针或引用去调用子类的接口,实现不同的方法或作用,这就是多态。

虚函数作用

虚函数的作用就是实现多态。

理解virtual关键字

virtual关键字是声明一个函数为虚函数,从而实现父类指针可以指向子类接口

#include <iostream>
using namespace std;
class Parent
{
public:
	void A() { cout << "Parent::A()" << endl; }
	virtual void B() { cout << "Parent::B()" << endl; }//虚函数
	virtual void C() { cout << "Parent::C()" << endl; }//虚函数
};

class Child: public Parent 
{
public:
	void A() { cout << "Child::A()" << endl; }
	void B() { cout << "Child::B()" << endl; }
	void C() { cout << "Child::C()" << endl; }
};

int main()
{
	Child obj;
	Parent *p = &obj;//父类指针指向子类对象

	p->A();	//relult: Parent::A() --->无virtual关键字,调用的是父类的接口
	p->B(); //relult: Child::B() --->因为virtual关键字,调用的是子类的接口
	p->C(); //relult: Child::C() --->因为virtual关键字,调用的是子类的接口
}

虚函数表

每个有虚函数的类都有一张虚函数表,这张虚函数表本质是一个函数指针数组,存放了这个类的虚函数的函数地址

class Parent
{
public:
	void A() { cout << "Parent::A()" << endl; }
	virtual void B() { cout << "Parent::B()" << endl; }//虚函数
	virtual void C() { cout << "Parent::C()" << endl; }//虚函数
};

在Parent这个类中,虚函数表中存储了 Parent中 B 和 Parent中 C 这两个虚函数的函数地址

地址Value
0x0001Parent 函数 B 的地址
0x0002Parent 函数 C 的地址
class Child: public Parent 
{
public:
	void A() { cout << "Child::A()" << endl; }
	void B() { cout << "Child::B()" << endl; }
};

在Child这个类中,重写了父类的B的虚函数,继承了父类的C的虚函数,只有重写了父类的虚函数,子类虚表才会存放重写的虚函数的地址,否则还是存放的父类的虚函数的地址,Child表中存储了Child中 B 和 Parent中 C 这两个虚函数的函数地址

地址Value
0x0001Child函数 B 的地址
0x0002Parent函数 C 的地址

在main()中,父类通过指向子类,调用的虚函数便从子类的虚表中找相应的接口

	Child obj;
	Parent *p = &obj;//父类指针指向子类对象

	p->A();	//relult: Parent::A() --->不是虚函数,不在子类的虚表中
	p->B(); //relult: Child::B() --->虚函数,调用子类虚表中的 B()
	p->C(); //relult: Parent::C() --->子类没有重写,调用父类的C()
这就是虚函数表的调用原理,我们也可以进行验证:
1. 将obj的地址强转为int *
2. 强转后取* ,得到虚函数表的地址: *((int* )&obj) ,这个地址是一个存放函数地址的数组
3. 声明一个指针,指向该数组: typedef void(*ptr)();	ptr *p = (ptr *)(*((int*)&obj));
4. 通过指针p可查看虚表的存储情况,虚表的最后存储的是nullptr
#include <iostream>
using namespace std;

typedef void(*ptr)();
class Parent
{
public:
	void A() { cout << "Parent::A()" << endl; }
	virtual void B() { cout << "Parent::B()" << endl; }//虚函数
	virtual void C() { cout << "Parent::C()" << endl; }//虚函数
};

class Child: public Parent 
{
public:
	void A() { cout << "Child::A()" << endl; }
	void B() { cout << "Child::B()" << endl; }//重写父类
};

int main()
{
	Child obj;
	Parent *p = &obj;//父类指针指向子类对象

	ptr *pt = (ptr *)(*((int*)&obj));

	for (int i = 0; nullptr != pt[i] ; i++)
	{
		pt[i]();
	}
	
	return 0;
}

在这里插入图片描述

因此,Child 的虚表存的是重写后的B的地址和继承的C的地址
文章介绍的不够详细,希望对大家有所帮助!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值