咱们说的虚表其实有不少种叫法:ios
这些都是虚表的意思。虚表是一种利用程序语言实现的dynamic dispatch机制,或者说runtime method binding机制,也就是咱们说的多态。数据结构
注:笔者在本文使用C++语言,而且统一用vTable来表示虚表。函数
用virtual关键字修饰的函数就叫虚函数。工具
由于vTable(虚表)是C++利用runtime来实现多态的工具,因此咱们须要借助virtual关键字将函数代码地址存入vTable来躲开静态编译期。这里咱们先不深刻探究,后面我会细说。spa
首先咱们先来看一个没有虚函数,即没有用到vTable的例子:.net
#include <iostream> #include <ctime> using std::cout; using std::endl; struct Animal { void makeSound() { cout << "动物叫了" << endl; } }; struct Cow : public Animal { void makeSound() { cout << "牛叫了" << endl; } }; struct Pig : public Animal { void makeSound() { cout << "猪叫了" << endl; } }; struct Donkey : public Animal { void makeSound() { cout << "驴叫了" << endl; } }; int main(int argc, const char * argv[]) { srand((unsigned)time(0)); int count = 4; while (count --) { Animal *animal = nullptr; switch (rand() % 3) { case 0: animal = new Cow; break; case 1: animal = new Pig; break; case 2: animal = new Donkey; break; } animal->makeSound(); delete animal; } return 0; }指针
1code 2对象 3blog 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#include <iostream> #include <ctime> using std::cout; using std::endl;
struct Animal { void makeSound() { cout << "动物叫了" << endl; } };
struct Cow : public Animal { void makeSound() { cout << "牛叫了" << endl; } }; struct Pig : public Animal { void makeSound() { cout << "猪叫了" << endl; } }; struct Donkey : public Animal { void makeSound() { cout << "驴叫了" << endl; } };
int main(int argc, const char * argv[]) { srand((unsigned)time(0)); int count = 4; while (count --) { Animal *animal = nullptr; switch (rand() % 3) { case 0: animal = new Cow; break; case 1: animal = new Pig; break; case 2: animal = new Donkey; break; } animal->makeSound(); delete animal; } return 0; } |
程序中有一个基类Animal
,它有一个makeSound()函数
。有三个继承自Animal的子类,分别是牛、猪、驴
,而且实现了本身的makeSound()方法
。很简单的代码,是吧。
咱们运行程序,你以为输出结果会是什么呢?不错,这里会连续执行4次Animal的makeSound()方法,结果以下:
为何?由于咱们的基类Animal的makeSound()方法没有使用Virtual修饰,因此在静态编译时就makeSound()的实现就定死了。调用makeSound()方法时,编译器发现这是Animal指针,就会直接jump到makeSound()的代码段地址进行调用。
ok,那么咱们把Animal的makeSound()改成虚函数,以下:
struct Animal { virtual void makeSound() { cout << "动物叫了" << endl; } };
1 2 3 4 5 6 |
struct Animal { virtual void makeSound() { cout << "动物叫了" << endl; } }; |
运行会是怎样?如你所料,多态已经成功实现:
接下来就是你们最关心的部分,这是怎么回事?编译器到底作了什么?
为了说明方便,咱们须要修改一下基类Animal的代码,不改变其余子类,修改以下:
struct Animal { virtual void makeSound() { cout << "动物叫了" << endl; } virtual void walk() {} void sleep() {} }; struct Cow : public Animal { void makeSound() { cout << "牛叫了" << endl; } }; struct Pig : public Animal { void makeSound() { cout << "猪叫了" << endl; } }; struct Donkey : public Animal { void makeSound() { cout << "驴叫了" << endl; } };
1 2 3 4 5 6 7 8 9 |
struct Animal { virtual void makeSound() { cout << "动物叫了" << endl; } virtual void walk() {} void sleep() {} };
struct Cow : public Animal { void makeSound() { cout << "牛叫了" << endl; } }; struct Pig : public Animal { void makeSound() { cout << "猪叫了" << endl; } }; struct Donkey : public Animal { void makeSound() { cout << "驴叫了" << endl; } }; |
首先咱们须要知道几个关键点:
咱们怎么理解?从本例来看,咱们的Animal、Cow、Pig、Donkey类都有本身的虚表,而且虚表里都有两个地址指针指向makeSound()和walk()的函数地址。一个指针4个字节,所以每一个vTable的大小都是8个字节。如图:
他们的虚表中记录着不一样函数的地址值。能够看到Cow、Pig、Donkey重写了makeSound()函数可是没有重写walk()函数。所以在调用makeSound()时,就会直接jump到本身实现的code Address。而调用walk()时,则会jump到Animal父类walk的Code Address。
如今咱们已经知道虚表的数据结构了,那么咱们在堆里实例化类对象时是怎么样调用到相应的函数的呢?这就要借助到虚指针了(vPointer)。
虚指针是类实例对象指向虚表的指针,存在于对象头部,大小为4个字节,好比咱们的Donkey类的实例化对象数据结构就以下:
咱们修改main函数里的代码,以下:
int main(int argc, const char * argv[]) { int count = 2; while (count --) { Animal *animal = new Donkey; animal->makeSound(); delete animal; } return 0; }
1 2 3 4 5 6 7 8 9 10 |
int main(int argc, const char * argv[]) { int count = 2; while (count --) { Animal *animal = new Donkey; animal->makeSound(); delete animal; } return 0; } |
咱们在堆中生成了两个Donkey实例,运行结果以下:
驴叫了 驴叫了 Program ended with exit code: 0
1 2 3 |
驴叫了 驴叫了 Program ended with exit code: 0 |
没问题。而后咱们再来看看堆里的结构,就变成了这样:
还有什么是这张图不能说明的呢?