C++——虚函数表——typedef指向函数的指针
一、typedef函数指针
- typedef void (*func)(void);//可以理解为定义了一个别名为func的函数指针,该指针指向一个入口参数和返回值类型均为void的函数
- 函数指针的形式:
- 返回值类型 (*函数名)(参数表)
-
#include <iostream> using namespace std; //定义一个函数指针pFUN,它指向一个返回类型为char,有一个整型的参数的函数 char (*pFun)(int); //定义一个返回类型为char,参数为int的函数 //从指针层面上理解该函数,即函数的函数名实际上是一个指针, //该指针指向函数在内存中的首地址 char glFun(int a) { cout << a; //return a; } int main() { //将函数glFun的地址赋值给变量pFun pFun = glFun; //*pFun”显然是取pFun所指向地址的内容,当然也就是取出了函数glFun()的内容,然后给定参数为2。 (*pFun)(2); return 0; } //函数指针的小用法
-
- typedef可以让函数指针更直观方便
- typedef 返回值类型(*新类型)(参数表)
-
1 typedef char (*PTRFUN)(int); 2 PTRFUN pFun; 3 char glFun(int a){ return;} 4 void main() 5 { 6 pFun = glFun; 7 (*pFun)(2); 8 }
- typedef的功能是定义新的类型。第一句话是定义了一种PTRFUN的类型,并定义这种类型为指向某种函数的指针,这种函数以一个int为参数,char为返回值类型。后边就可以像使用int,float,char一样使用PTRFUN了。
- 第二行的代码便使用这个新类型定义了变量pFun,此时就可以上上边的例子一样使用这个变量了。
- typedef可以让函数指针更直观方便
2.虚函数表——注意:父类与子类的虚函数表是不同的,不是同一个表。
1、虚函数就是通过一张虚函数表实现的。简称为V-Table。在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承覆盖的问题。
- 类的虚函数的调用是通过虚函数表实现的。所谓的虚函数表,是编译器自动为一个带有虚函数的类生成的一块内存空间,其中存储着每一个虚函数的入口地址。由于函数的入口地址可以看成一个指针类型,因此这些虚函数的地址间隔为四个字节(32位操作系统)。而每一个带有虚函数的类的实例,都拥有一个虚函数指针-VPTR,在类的对象初始化完毕后,它指向虚函数表。这个vptr指针位于类对象的首部,即作为第一个成员变量。
- C++标准并没有规定虚函数的实现方法,使用虚函数表的方法是编译器厂商制定的。
2、举个例子:
-
1 class Base { 2 public: 3 virtual void f() { cout << "Base::f" << endl; } 4 virtual void g() { cout << "Base::g" << endl; } 5 virtual void h() { cout << "Base::h" << endl; } 6 }; 7 9 typedef void(*Fun)(void);//定义一个指向参数类型与返回值类型都为void的函数指针 10 Base b; 11 Fun pFun = NULL; 12 cout << "虚函数表地址:" << (int*)(&b) << endl; 13 cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl; 14 // Invoke the first virtual function 15 pFun = (Fun)*((int*)*(int*)(&b)); 16 pFun(); 17 //运行结果如下: 18 虚函数表地址:0012FED4 19 虚函数表 — 第一个函数地址:0044F148 20 Base::f
-
- 通过上边的例子,强行把&b转成int*,取得虚函数表的地址,然后再次取地址就得到第一个虚函数的地址了,也就是Base::f(),通过这个实例,我们可以知道,如果要调用Base::g()和Base::h(),其代码如下
-
1 (Fun)*((int*)*(int*)(&b)+0); // Base::f() 2 (Fun)*((int*)*(int*)(&b)+1); // Base::g() 3 (Fun)*((int*)*(int*)(&b)+2); // Base::h()
-
3、图解虚函数表:
- 左边是一个含有虚函数的类的实例,即类对象,对象的首部存放的是一个指向虚函数表的指针即vptr指针,vptr的内容即虚函数表的首地址。右边是编译器为含有虚函数的类生成的一块内存空间,上边存储着每一个虚函数的入口地址。上图中,在虚函数的结尾多了一个.,这时虚函数表的结束结点。类似字符串的结束符\0,标志着虚函数表的结束。
4、一般的继承(无虚函数覆盖)的虚函数表
- 假如有如右图的一个继承关系
在这个类的继承关系中,子类没有重载任何父类的函数,那么在派生类的实例中,其虚函数表如下图所示:
对于派生类的对象:Derive d;其虚函数表如下图
- 虚函数按照其声明顺序存放在表中
- 父类的虚函数在子类的前面
5、有虚函数覆盖的虚函数表
覆盖父类的虚函数是很显然的事情,不然虚函数就变得毫无存在地意义。如果在类中有虚函数重载了父类的虚函数,我们假设右下边的一个继承关系:
- 为了可以看到被继承过后的效果,在这个类的设计中,只覆盖了父类的一个函数f(),对于此派生类的对象其虚函数表将是下边这个样子:
-
-
-
- 覆盖的f()函数被放到了虚函数表原来父类虚函数的位置。没有被覆盖的函数依旧存在。
-
-
-
1 Base *b = new Derive(); 2 b->f(); 3 4 //由于b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数的地址所取代,于是在实际的调用过程中是Derive::f()被调用的,这就实现了多态。
父类与子类的虚函数表是不一样的
6、多重继承(无虚函数覆盖)
- 类的继承关系与派生类的对象的虚函数表分别如下图
-
- 虚函数表如下:
-
- 每个父类都有自己的虚函数表
- 子类的成员函数被放到了第一个父类的表中。(第一个父类即按照声明的顺序判断的)
- 这么做是为了解决不同的父类型的指针指向同一个子类实例,而能够调用到实际的函数
7、多重继承(有虚函数覆盖)
- 类的继承关系与派生类的对象的虚函数表分别如下:
- 派生类实例的虚函数表如下:
-
- 可以看到,三个父类虚函数表中的f()的位置被替换成了子类的函数指针,这样,我们就可以使用任一个父类的指针来指向子类并调用子类的f()函数了。
-
1 Derive d; 2 Base1 *b1 = &d; 3 Base2 *b2 = &d; 4 Base3 *b3 = &d; 5 6 b1->f(); //Derive::f() 7 b2->f(); //Derive::f() 8 b3->f(); //Derive::f() 9 10 b1->g(); //Base1::g() 11 b2->g(); //Base2::g() 12 b3->g(); //Base3::g()