介绍虚函数表前先熟悉一下什么是多态,虚函数以及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 |
---|---|
0x0001 | Parent 函数 B 的地址 |
0x0002 | Parent 函数 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 |
---|---|
0x0001 | Child函数 B 的地址 |
0x0002 | Parent函数 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的地址
文章介绍的不够详细,希望对大家有所帮助!