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,此时就可以上上边的例子一样使用这个变量了。



 

 

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()

       

 

posted @ 2019-01-16 20:16  long_ago  阅读(1655)  评论(1编辑  收藏  举报