zoukankan      html  css  js  c++  java
  • 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的内容即虚函数表的首地址。右边是编译器为含有虚函数的类生成的一块内存空间,上边存储着每一个虚函数的入口地址。上图中,在虚函数的结尾多了一个.,这时虚函数表的结束结点。类似字符串的结束符,标志着虚函数表的结束。

        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()
  • 相关阅读:
    存储那些事儿(二): 下一代Linux文件系统BTRFS简介
    RabbitMQ消息队列的小伙伴: ProtoBuf(Google Protocol Buffer)
    RabbitMQ消息队列(七):适用于云计算集群的远程调用(RPC)
    RabbitMQ消息队列(六):使用主题进行消息分发
    C++内存管理之shared_ptr
    C++程序调试方式总结
    匿名对象?临时对象?
    C++多态中虚函数表合并与继承问题
    C++继承体系中的内存分段
    C++继承体系中的内存对齐
  • 原文地址:https://www.cnblogs.com/southcyy/p/10279152.html
Copyright © 2011-2022 走看看