c++虚函数

来源:互联网 发布:半岛雪人 mac 编辑:程序博客网 时间:2024/06/14 04:53
异步赠书:9月重磅新书升级,本本经典    SDCC 2017之区块链技术实战线上峰会    程序员9月书讯    每周荐书:ES6、虚拟现实、物联网(评论送书)

C++ 虚函数表解析

标签: c++funclass编译器语言iostream
319789人阅读 评论(450)收藏举报
分类:
作者同类文章X

    目录(?)[+]

    1. 前言
    2. 虚函数表
    3. 一般继承无虚函数覆盖
    4. 一般继承有虚函数覆盖
    5. 多重继承无虚函数覆盖
    6. 多重继承有虚函数覆盖
    7. 安全性
    8. 结束语
    9. 附录一VC中查看虚函数表
    10. 附录 二例程

    C++ 虚函数表解析

     

    陈皓

    http://blog.csdn.net/haoel

     

     

    前言

     

    C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。

     

     

    关于虚函数的使用方法,我在这里不做过多的阐述。大家可以看看相关的C++的书籍。在这篇文章中,我只想从虚函数的实现机制上面为大家一个清晰的剖析。

     

    当然,相同的文章在网上也出现过一些了,但我总感觉这些文章不是很容易阅读,大段大段的代码,没有图片,没有详细的说明,没有比较,没有举一反三。不利于学习和阅读,所以这是我想写下这篇文章的原因。也希望大家多给我提意见。

     

    言归正传,让我们一起进入虚函数的世界。

     

     

    虚函数表

     

    C++了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

     

    这里我们着重看一下这张虚函数表。C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

     

    听我扯了那么多,我可以感觉出来你现在可能比以前更加晕头转向了。没关系,下面就是实际的例子,相信聪明的你一看就明白了。

     

    假设我们有这样的一个类:

     

    class Base {

        public:

               virtual void f() { cout <<"Base::f" << endl; }

               virtual void g() { cout <<"Base::g" << endl; }

               virtual void h() { cout <<"Base::h" << endl; }

     

    };

     

    按照上面的说法,我们可以通过Base的实例来得到虚函数表。下面是实际例程:

     

             typedefvoid(*Fun)(void);

     

               Base b;

     

               Fun pFun = NULL;

     

               cout << "虚函数表地址:" << (int*)(&b) << endl;

               cout << "虚函数表第一个函数地址:" << (int*)*(int*)(&b) << endl;

     

               // Invoke the first virtual function 

               pFun = (Fun)*((int*)*(int*)(&b));

               pFun();

     

    实际运行经果如下:(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)

     

    虚函数表地址:0012FED4

    虚函数表第一个函数地址:0044F148

    Base::f

     

     

    通过这个示例,我们可以看到,我们可以通过强行把&b转成int *,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证(把int*强制转成了函数指针)。通过这个示例,我们就可以知道如果要调用Base::g()Base::h(),其代码如下:

     

               (Fun)*((int*)*(int*)(&b)+0); // Base::f()

               (Fun)*((int*)*(int*)(&b)+1); // Base::g()

               (Fun)*((int*)*(int*)(&b)+2); // Base::h()

     

    这个时候你应该懂了吧。什么?还是有点晕。也是,这样的代码看着太乱了。没问题,让我画个图解释一下。如下所示:

    注意:在上面这个图中,我在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“/0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。

     

     

    下面,我将分别说明“无覆盖”和“有覆盖”时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的。我之所以要讲述没有覆盖的情况,主要目的是为了给一个对比。在比较之下,我们可以更加清楚地知道其内部的具体实现。

     

    一般继承(无虚函数覆盖)

     

    下面,再让我们来看看继承时的虚函数表是什么样的。假设有如下所示的一个继承关系:

     

     

    请注意,在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例中,其虚函数表如下所示:

     

    对于实例:Derive d;的虚函数表如下:

     

    我们可以看到下面几点:

    1)虚函数按照其声明顺序放于表中。

    2)父类的虚函数在子类的虚函数前面。

     

    我相信聪明的你一定可以参考前面的那个程序,来编写一段程序来验证。

     

     

     

    一般继承(有虚函数覆盖)

     

    覆盖父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。下面,我们来看一下,如果子类中有虚函数重载了父类的虚函数,会是一个什么样子?假设,我们有下面这样的一个继承关系。

     

     

     

    为了让大家看到被继承过后的效果,在这个类的设计中,我只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:

     

     

    我们从表中可以看到下面几点,

    1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。

    2)没有被覆盖的函数依旧。

     

    这样,我们就可以看到对于下面这样的程序,

     

               Base *b = new Derive();

     

               b->f();

     

    b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。

     

     

     

    多重继承(无虚函数覆盖)

     

    下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。

     

     

     

    对于子类实例中的虚函数表,是下面这个样子:

     

    我们可以看到:

    1) 每个父类都有自己的虚表。

    2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

     

    这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

     

     

     

     

    多重继承(有虚函数覆盖)

     

    下面我们再来看看,如果发生虚函数覆盖的情况。

     

    下图中,我们在子类中覆盖了父类的f()函数。

     

     

     

    下面是对于子类实例中的虚函数表的图:

     

     

    我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:

     

               Derive d;

               Base1 *b1 = &d;

               Base2 *b2 = &d;

               Base3 *b3 = &d;

               b1->f(); //Derive::f()

               b2->f(); //Derive::f()

               b3->f(); //Derive::f()

     

               b1->g(); //Base1::g()

               b2->g(); //Base2::g()

               b3->g(); //Base3::g()

     

     

    安全性

     

    每次写C++的文章,总免不了要批判一下C++。这篇文章也不例外。通过上面的讲述,相信我们对虚函数表有一个比较细致的了解了。水可载舟,亦可覆舟。下面,让我们来看看我们可以用虚函数表来干点什么坏事吧。

     

    一、通过父类型的指针访问子类自己的虚函数

    我们知道,子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数:

     

             Base1 *b1 = new Derive();

               b1->f1();  //编译出错

     

    任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。(关于这方面的尝试,通过阅读后面附录的代码,相信你可以做到这一点)

     

     

    二、访问non-public的虚函数

    另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。

     

    如:

     

    class Base {

       private:

               virtual void f() { cout <<"Base::f" << endl; }

     

    };

     

    class Derive :public Base{

     

    };

     

    typedefvoid(*Fun)(void);

     

    void main() {

       Derive d;

       Fun  pFun = (Fun)*((int*)*(int*)(&d)+0);

       pFun();

    }

     

     

    结束语

    C++这门语言是一门Magic的语言,对于程序员来说,我们似乎永远摸不清楚这门语言背着我们在干了什么。需要熟悉这门语言,我们就必需要了解C++里面的那些东西,需要去了解C++中那些危险的东西。不然,这是一种搬起石头砸自己脚的编程语言。

     

    在文章束之前还是介绍一下自己吧。我从事软件研发有十个年头了,目前是软件开发技术主管,技术方面,主攻Unix/C/C++,比较喜欢网络上的技术,比如分布式计算,网格计算,P2PAjax等一切和互联网相关的东西。管理方面比较擅长于团队建设,技术趋势分析,项目管理。欢迎大家和我交流,我的MSNEmail是:haoel@hotmail.com 

     

    附录一:VC中查看虚函数表

     

    我们可以在VCIDE环境中的Debug状态下展开类的实例就可以看到虚函数表了(并不是很完整的)

    附录二:例程

    下面是一个关于多重继承的虚函数表访问的例程:

     

    #include<iostream>

    usingnamespace std;

     

    class Base1 {

    public:

               virtual void f() { cout <<"Base1::f" << endl; }

               virtual void g() { cout <<"Base1::g" << endl; }

               virtual void h() { cout <<"Base1::h" << endl; }

     

    };

     

    class Base2 {

    public:

               virtual void f() { cout <<"Base2::f" << endl; }

               virtual void g() { cout <<"Base2::g" << endl; }

               virtual void h() { cout <<"Base2::h" << endl; }

    };

     

    class Base3 {

    public:

               virtual void f() { cout <<"Base3::f" << endl; }

               virtual void g() { cout <<"Base3::g" << endl; }

               virtual void h() { cout <<"Base3::h" << endl; }

    };

     

     

    class Derive :public Base1, public Base2,public Base3 {

    public:

               virtual void f() { cout <<"Derive::f" << endl; }

               virtual void g1() { cout <<"Derive::g1" << endl; }

    };

     

     

    typedefvoid(*Fun)(void);

     

    int main()

    {

               Fun pFun = NULL;

     

               Derive d;

               int** pVtab = (int**)&d;

     

               //Base1's vtable

               //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0);

               pFun = (Fun)pVtab[0][0];

               pFun();

     

               //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1);

               pFun = (Fun)pVtab[0][1];

               pFun();

     

               //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2);

               pFun = (Fun)pVtab[0][2];

               pFun();

     

               //Derive's vtable

               //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3);

               pFun = (Fun)pVtab[0][3];

               pFun();

     

               //The tail of the vtable

               pFun = (Fun)pVtab[0][4];

               cout<<pFun<<endl;

     

     

               //Base2's vtable

               //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);

               pFun = (Fun)pVtab[1][0];

               pFun();

     

               //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);

               pFun = (Fun)pVtab[1][1];

               pFun();

     

               pFun = (Fun)pVtab[1][2];

               pFun();

     

               //The tail of the vtable

               pFun = (Fun)pVtab[1][3];

               cout<<pFun<<endl;

     

     

     

               //Base3's vtable

               //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);

               pFun = (Fun)pVtab[2][0];

               pFun();

     

               //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);

               pFun = (Fun)pVtab[2][1];

               pFun();

     

               pFun = (Fun)pVtab[2][2];

               pFun();

     

               //The tail of the vtable

               pFun = (Fun)pVtab[2][3];

               cout<<pFun<<endl;

     

               return 0;

    }

     

    (转载时请注明作者和出处。未经许可,请勿用于商业用途)

     

    更多文章请访问我的Blog:http://blog.csdn.net/haoel

     
    73
    3
     
     

      相关文章推荐
    • 强力Django 和杀手级xadmin[视频]
    • 携程机票大数据基础平台架构演进-- 许鹏
    • 关注CSDN程序人生公众号,轻松获得下载积分
    • Python可以这样学--董付国
    • 打造最漂亮的串口通讯调试助手 基于C# WPF .net4开发 附源码带详细注释
    • 一步一步学Spring Boot
    • Shiro学习示例,以及与SpringMVC整合,标签注解等
    • 深入浅出C++程序设计
    • C# 串口调试小助手(有源码)
    • Android Material Design 新控件
    • Toad for oracle 12.8 中文绿色注册版
    • 机器学习需要用到的数学知识
    • 大型天地劫WEB网页游戏源码(asp+access版)
    • xshell5旧版本(无需购买)
    • 图片取点工具 取得图片中某点的坐标
    • 新手如何在CSDN(中国软件开发联盟)网赚取资源积分

    查看评论
    68楼 Zaki_huang4天前 15:31发表 [回复] [引用][举报]
    自己做了一遍, 学到很多, 我的博客
    http://www.jianshu.com/writer#/notebooks/15612794/notes/17182281/preview
    67楼 诶阿星2017-09-13 14:32发表 [回复] [引用][举报]
    原来你才是原创呀,谢谢分享,受教了!
    66楼 feifeiiong2017-09-08 17:56发表 [回复] [引用][举报]
    还有,能看到本条评论的诸位请直接移步 深入探索 C++ 对象模型 看这些所谓专家不知道哪里来的结论真的误人子弟
    65楼 feifeiiong2017-09-07 21:16发表 [回复] [引用][举报]
    事实上,楼主写的一开始就是错的,虚函数表是类对象之间共享的,而非每个对象都保存了一份,楼主得到的也只是虚指针的地址,而非虚函数表的地址,事实上对虚函数指针的地址解引用得到的才是虚函数表的地址(因为虚函数指针指向虚函数表),以上经过理论和实际验证。
    Re: CNfreeee3天前 16:17发表 [回复] [引用][举报]
    回复feifeiiong:需要先转成一个二级指针,再解引用才是对的
    *(int **)(&amp;b)
    Re: feifeiiong2017-09-08 11:07发表 [回复] [引用][举报]
    回复feifeiiong:强烈建议楼主验证自己的代码,楼主身份为博客专家,希望能够对自己的文章负责,谢谢!
    64楼 github_337059582017-05-20 20:01发表 [回复] [引用][举报]
    多重继承的虚表图是错的。好坑
    Re: a_big_pig2017-07-27 17:28发表 [回复] [引用][举报]
    回复github_33705958:xcode + mac 验证是对的啊,大兄弟你是不是哪里弄错了
    63楼 shuzhengjiang2017-05-09 16:26发表 [回复] [引用][举报]
    不为困穷而改节,这句话应该改成,(识时务者为俊杰!物竞天择,适者生存)
    62楼 shuzhengjiang2017-05-09 16:25发表 [回复] [引用][举报]
    不为困穷而改节,这个我觉得要有句话,识时务者为俊杰!
    Re: qq_356875162017-08-13 10:37发表 [回复] [引用][举报]
    回复shuzhengjiang:写的挺好
    61楼 HymanLiuTS2017-03-22 16:23发表 [回复] [引用][举报]
    有一个问题,刚刚看<<深度探索C++对象模型>>,它在第9页所说每个类所关联的type-info object会放到虚函数表的第一位,但是楼主的文章中提到表格的第一位放的是虚函数,不过代码验证楼主是对的,难道书中出错了????
    Re: pkxpp2017-04-21 11:14发表 [回复] [引用][举报]
    回复lzhui1987:没去试验过,我的理解是:类对象里面的指针指的并不是虚表的起始地址,而是第一个函数地址项的地址。比如说type-info是放在表的第0项,第一个虚函数放在第1项,类对象的vptr指的是第1项所在的地址而已。
    60楼 小朋友872016-10-15 17:58发表 [回复] [引用][举报]
    把重载改成重写
    59楼 stevewongbuaa2016-09-06 16:42发表 [回复] [引用][举报]
    写的很好,不过如果要在64位机器运行要把int换成long才行
    58楼 lonely_geek2016-09-05 20:53发表 [回复] [引用][举报]
    讲的通俗易懂,但是笔者 对于 重载 和 覆盖 没有区分开,这点不应该了啊。多态是基于 函数的 覆盖,不是重载
    57楼 khing2016-08-21 22:26发表 [回复] [引用][举报]
    用VC试了下“一般继承(无虚函数覆盖)”,很惊讶地看到虚函数表里面并没有Derived的函数,大家可以试试
    Re: jinshangyu2017-02-27 18:20发表 [回复] [引用][举报]
    回复khing:你肯定是继承写错了 少了virtual关键字
    56楼 Annoymous_2016-07-18 21:32发表 [回复] [引用][举报]
    多重继承部分Not Even Wrong! 多重继承的时候调用函数是要修正地址的

    后面的两段安全性批评更是莫名其妙,要让程序不正常 直接用 (int*)0=0 就可以了,费这么大的功夫到底是在想批判什么
    55楼 huisha252016-06-30 15:00发表 [回复] [引用][举报]
    你好,今天看了你的虚函数讲解,受益匪浅。我从事c++开发也有一段时间了,想找个大牛学习学习,能加个好友吗?
    QQ:872569904
    期待您的回复,谢谢!!!
    54楼 Bingogoc2016-06-28 16:43发表 [回复] [引用][举报]
    cout << "虚函数表地址:" << (int*)(&b) << endl;
    cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl;
    这个应该改成:
    cout << "虚函数表地址:" << *((long*)(&b) )<< endl;
    cout << "虚函数表 — 第一个函数地址:" << *((long*)*(long*)(&b) )<< endl;
    这样就能解决32位系统与64位系统的问题,两个地址应分别在原有的基础上加*,本人拙见
    Re: 编程界的小学生2017-08-08 09:34发表 [回复] [引用][举报]
    回复Bingogoc: 刚开始还以为之前的理解不对呢 看了这篇文章 335楼说的对 我就是这么理解的 该博文还是有错的 希望改之
    Re: jackqk2016-10-14 11:08发表 [回复] [引用][举报]
    回复Bingogoc:技术还是得自己学习,别做梦了,大家都有事,陪陪家人也不会教你
    53楼 qq_350532672016-05-31 00:07发表 [回复] [引用][举报]
    是重写不是重载吧?
    52楼 Shepsee2016-05-08 17:34发表 [回复] [引用][举报]
    非常清晰的解释 与插图 太感谢了
    51楼 maowei1172016-04-30 16:35发表 [回复] [引用][举报]
    我认为:
    [cpp] view plain copy
    print?
    1. (int*)&b  
    (int*)&b

    得到的不是虚函数表的地址,而是虚函数指针的地址。
    Re: whdugh2016-08-15 22:19发表 [回复] [引用][举报]
    回复maowei117:对象取地址也不是虚函数的指针吧
    Re: g360z247j1232016-07-14 17:39发表 [回复] [引用][举报]
    回复maowei117:我也认为如此。楼主原文说”C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置“,如果编译器真是这样做,那么对实例对象Base b取地址(&b),得到的是指向虚函数表的指针的地址ptr_addr;对该地址解引用(*ptr_addr)得到指向虚函数表的指针的值(即虚函数表的地址)Vtable_addr
    50楼 ProLianGee2016-04-03 13:23发表 [回复] [引用][举报]
    好文章。解决了我很多疑惑。
    49楼 JRSmith72016-04-01 20:14发表 [回复] [引用][举报]
    请教下,例程代码的最后一个模块:
    [cpp] view plain copy
    print?
    1. //The tail of the vtable  
    2. pFun = (Fun)pVtab[2][3];  
    3. cout<<pFun<<endl;  
                //The tail of the vtable            pFun = (Fun)pVtab[2][3];            cout<<pFun<<endl;

    为什么输出是1?已经是最后一个虚函数表了,不应该输出0吗?
    48楼 chenyang12312016-03-29 00:08发表 [回复] [引用][举报]
    个人觉得关于打印虚函数表地址与直接使用虚函数表调用虚函数部分有点错误,附上自己测试过的代码。评论太多了,不知道有没有人发现这个错误,望楼主在博文里改正之,否则容易误人子弟。(如果不是我理解错的话)
    [cpp] view plain copy
    print?
    1. cout << "虚函数表的地址" << *((int *)&base) << endl;  
    2.   
    3. ((fun)(*((int *)(*(int *)(&base)) + 0)))();  
    4. ((fun)(*((int *)(*(int *)(&base)) + 1)))();  
    5. ((fun)(*((int *)(*(int *)(&base)) + 2)))();  
    cout << "虚函数表的地址" << *((int *)&base) << endl;((fun)(*((int *)(*(int *)(&base)) + 0)))();((fun)(*((int *)(*(int *)(&base)) + 1)))();((fun)(*((int *)(*(int *)(&base)) + 2)))();
    Re: zhangjin7392016-09-29 23:25发表 [回复] [引用][举报]
    回复chenyang1231:不用改代码, 楼主应该意思是 虚函数表地址的地址罢了
    Re: xingaide5202016-06-08 15:21发表 [回复] [引用][举报]
    回复chenyang1231:能解释下吗,我对这个用法不理解,太多指针了
    47楼 路过少年2016-01-24 18:57发表 [回复] [引用][举报]
    mark
    46楼 01_Terry2015-12-27 16:55发表 [回复] [引用][举报]
    写的真心好,感谢分享!
    45楼 大毛爱吃鱼2015-10-29 16:06发表 [回复] [引用][举报]
    有个问题,关于多重继承(有虚函数覆盖)。继承有几个父类就会有几个虚函数表,然后覆盖的虚函数会取代原来父类的对应的虚函数指针。可是继承的类对象,未覆盖的那些函数并没有在虚函数表中??
    44楼 andy当我遇上你2015-10-13 18:47发表 [回复] [引用][举报]
    谢谢楼主,我收藏了!
    43楼 白白皎皎2015-09-30 10:22发表 [回复] [引用][举报]
    大神,学习了
    42楼 改变oo2015-09-08 15:24发表 [回复] [引用][举报]
    学习了
    41楼 jerome0292015-09-01 20:15发表 [回复] [引用][举报]
    顶神牛!
    40楼 woshiyisang2015-08-20 17:21发表 [回复] [引用][举报]
    想问一下:虚函数表的内存是什么时候分配的?类定义的时候?
    39楼 rudy_yuan2015-08-17 07:20发表 [回复] [引用][举报]
    楼主的代码似乎有点问题?

    这是我写的可以运行的代码:http://www.rudy-yuan.net/archives/128/

    大家可以参考下,便于理解。。。
    38楼 Change_Land2015-08-06 12:53发表 [回复] [引用][举报]
    陈老师您好,这里我有一个疑问,就是我们不能在C++的构造函数中调用虚函数,即便是调用了,我们调用到的也是基类本身的那个函数,例如:
    class Transation
    {
    public:
    Transation()
    {
    logTransation();
    }

    virtual void logTransation()
    {
    cout << "call Transation::logTransation" << endl;
    }
    };

    class BuyTransation : public Transation
    {
    public:
    void logTransation() override
    {
    cout << "call BuyTransation::logTransation" << endl;
    }
    };

    class SaleTransation : public Transation
    {
    public:
    void logTransation() override
    {
    cout << "call SaleTransation::logTransation" << endl;
    }
    };

    void main(int argc, char* argv[])
    {
    BuyTransation buy;
    SaleTransation sale;
    }
    输出的结果为:
    call Transation::logTransation
    call Transation::logTransation
    按道理,此时调用logTransation函数的地址已经被派生类的那个logTransation函数的地址替换了,但是并没有出现这样的情况,那么这个虚函数表的赋值逻辑是怎样的呢?
    Re: 从来不作2015-08-07 14:54发表 [回复] [引用][举报]
    回复Manistein:对象在构造的时候,首先构造它的基类部分,然后构造自身。你举的例子应该是这样运行的:首先构造基类,设定vptr初值,然后调用基类构造函数,最后构造自身,重新设定vptr的值。
    37楼 Lilyss212015-07-26 21:17发表 [回复] [引用][举报]
    学习了,记录下。
    36楼 稚枭天卓2015-07-26 15:55发表 [回复] [引用][举报]
    请教下,虚函数表是一个类的对象共享,还是一个对象就拥有一个虚函数表?
    Re: 从来不作2015-08-07 14:56发表 [回复] [引用][举报]
    回复u013630349:每个类一个虚表,每个类的实例拥有一个指向虚表的指针。
    Re: lsfv0012016-06-11 15:54发表 [回复] [引用][举报]
    不错,不过有个地方.
    当类有 数据的时候.
    比如int .
    我的编译器
    会这样排.
    第一个虚表的地址.
    int 的数据
    第一个虚表的地址.
    35楼 tppppppppp12015-06-23 16:30发表 [回复] [引用][举报]
    我做实验的时候发现子类继承多个父类的时候子类对象的内存中存放的是“父类1虚函数表,父类1成员变量,父类2虚函数表,父类2成员变量。。。”,为何博主的图中的顺序是“父类1虚函数表,父类2虚函数表,成员变量”?
    34楼 Hellen11012015-06-01 23:37发表 [回复] [引用][举报]
    不错
    33楼 Hellen11012015-06-01 23:38发表 [回复] [引用][举报]
    不错
    32楼 smzx_boy20122015-05-23 23:59发表 [回复] [引用][举报]
    困扰许久的问题终于被解决了,多谢分享,希望我也有一天成长到分享这样的知识的人
    31楼 东东同学2015-05-06 21:38发表 [回复] [引用][举报]
    https://github.com/mudongliang/CppLearn
    其中testvtable*.cpp都是关于这个文章中几个例子写的demo,有什么问题,大家给我提一下!
    另外看见有人说第一个代码有问题,不知道是什么意思?在我64bit和32bit机器上都运行没问题啊!
    Re: s9855079952016-01-14 15:07发表 [回复] [引用][举报]
    回复mudongliangabcd:本来想把代码也给你放到github上的,提交试了下没权限。贴出来修改代码。
    #include<iostream>
    using namespace std;

    class Base{
    public:
    Base(){}
    // virtual ~Base(){}
    virtual void f(int a, int b){
    cout<<"Base::f()"<<endl;
    int c = a + b;
    cout << "add " << c << endl;
    }
    virtual void g(){
    cout<<"Base::g()"<<endl;
    }
    virtual void h(){
    cout<<"Base::h()"<<endl;
    }
    };

    typedef void(*Fun)(int, int);

    int main(){
    Base b;
    Fun pFun = NULL;
    cout << "虚函数地址:"<<(int *)(&b)<<endl;
    cout << "虚函数-第一个函数地址:"<<(int *)*(int *)(&b) <<endl;

    pFun = (Fun)*((int *)*(int *)(&b));
    pFun(3, 4);
    return 0;
    }
    Re: s9855079952016-01-14 15:02发表 [回复] [引用][举报]
    回复mudongliangabcd:1. 上述所有代码都不带构造和析构函数。(如果类中有虚函数,析构函数同样要设计成需函数,否则可能会造成内存泄漏)
    在加入虚析构函数后,按照上述逻辑,在64位机上跑testvtable.cpp,会有一些问题,你可以尝试一下;
    2. 即便如文中所述,找到了虚函数的函数地址。可以尝试在函数内部做运算,我在64位机上,运算结果没有跑对的。这个结果还不知道有没有可以帮忙解释下。
    30楼 chenzhg332015-04-02 23:46发表 [回复] [引用][举报]
    cout << "虚函数表地址:" << (int*)(&b) << endl;
    这句有问题吧?&b是b对象的地址,就算强制转为int*,还是b的地址,虚函数表地址应该是*(int*)&b,对于64位机器应该是*(long*)&b
    Re: 契约无罪2015-07-25 11:50发表 [回复] [引用][举报]
    cout << "虚函数表地址:" << (int*)(&b) << endl;
    cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl;
    其实改成
    cout<<" pV-Table = "
    cout<<" V-Table" 或者 “ V-Table[0]
    Re: Narsil2015-04-03 17:29发表 [回复] [引用][举报]
    回复u010078776:没错,这里很显然错误了,当然,无论 32 位还是 64 位,我们使用 intptr_t 就可以通用了
    29楼 wangzhongjunjun2015-03-25 16:36发表 [回复] [引用][举报]
    请教下大神:上面的图片用什么思维导图软件做出来的?
    28楼 bama24883137162015-03-23 10:46发表 [回复] [引用][举报]
    博客写的非常好 但是想问下如果通过普通指针定位到虚函数位置,并且通过普通指针调用该虚函数,如何在虚函数中访问对象的成员变量呢 我知道用类的成员函数指针取代普通指针可以做到。
    27楼 ixshells2015-03-12 16:06发表 [回复] [引用][举报]
    好文章,Mark一下
    26楼 9697222432015-01-18 16:51发表 [回复] [引用][举报]
    非常好,学习了,多来几篇这样用的文章
    25楼 sunny_ss122014-12-20 12:28发表 [回复] [引用][举报]
    请好, 对于虚函数表中虚函数的地址是不是应该写成
    (Fun)*((int**)*(int**)(&b)+0); // Base::f()
    (Fun)*((int**)*(int**)(&b)+1); // Base::g()
    (Fun)*((int**)*(int**)(&b)+2); // Base::h()
    否则在64位机器下是有错的。
    Re: hahaxiaohuo20152015-02-02 14:41发表 [回复] [引用][举报]
    回复sunny_ss12:同意,但是int**这样很难理解,这位大神能否详细解释下
    (Fun)*((int**)*(int**)(&b))的含义呢?多谢多谢
    Re: sunny_ss122015-02-26 17:01发表 [回复] [引用][举报]
    回复sunyekuan_cafuc:而(int*)(&b)相当于把Base的第一个元素看成了int,在32位机器下int和指针都是4个字节不会出问题,而在64位机器下,int和指针占用的空间大小不同,int是4个字节,而在64位机器下指针是8个字节,这样转换就有问题了
    Re: sunny_ss122015-02-26 16:57发表 [回复] [引用][举报]
    回复sunyekuan_cafuc:我是这样想的:Base对象空间存储的第一个元素是虚函数表地址,即一个指针变量,对Base对象取地址实际上就是对该指针变量取地址,即应该是指向指针的指针,所以是(int**)(&b)而不是(int*)(&b)。不知道说的对不对
    Re: chenzhg332015-04-02 23:25发表 [回复] [引用][举报]
    回复sunny_ss12:关键在于int** ptr是指向指针的指针,ptr+1就是移动8个字节,如果(Fun)*((int*)*(int*)(&b)+2); 也是移动8个字节,就是f函数地址
    24楼 jiangjun12345shi2014-12-13 14:39发表 [回复] [引用][举报]
    随便复制别人的 真没意思
    23楼 jiangjun12345shi2014-12-13 14:38发表 [回复] [引用][举报]
    随便复制别人的 没他妈的意思
    Re: syj524172015-03-05 22:16发表 [回复] [引用][举报]
    回复jiangjun12345shi:那你能给下原始链接?
    22楼 cheyiliu2014-12-11 17:17发表 [回复] [引用][举报]
    学习了
    21楼 hothuangting2014-12-02 09:00发表 [回复] [引用][举报]
    好啊
    20楼 忧伤还是快乐2014-11-20 08:32发表 [回复] [引用][举报]
    您好:麻烦请问一下即使访问到了子类中非继承的函数和访问到了基类中的private或者protected的函数,这样做有什么实际的意义?谢谢
    19楼 827fast2014-11-15 02:06发表 [回复] [引用][举报]
    大体上是对的。有一个地方误导人啊。
    18楼 damotiansheng2014-11-10 00:16发表 [回复] [引用][举报]
    崩溃,第一个代码就有两个错误,竟然没人提,搞得我没看懂,一直纠结
    17楼 Full_Speed_Turbo2014-10-29 10:14发表 [回复] [引用][举报]
    讲解的很清楚,通俗易懂!博主V5!
    16楼 johnnyforonline2014-10-23 13:41发表 [回复] [引用][举报]
    mark
    15楼 Sylvanas_XV2014-10-14 00:50发表 [回复] [引用][举报]
    挺好的
    14楼 JakeMiao2014-10-05 11:01发表 [回复] [引用][举报]
    很好。但是重载、重写、覆盖三者没有分清楚。
    13楼 xuweiqun2014-09-30 14:48发表 [回复] [引用][举报]
    今天在 vs2003 and vs2013 + win7 下测试,发现一个小问题:
    经过多次 debug release测试,虚函数表的结束结点 是个不确定值。
    12楼 liaoruiyan2014-09-28 18:53发表 [回复] [引用][举报]
    其实应该解释下(Fun)*((int*)*(int*)(&d)+0);这样的代码,不然看完整篇博文还是迷迷糊糊不懂虚函数表
    11楼 Sylvernass2014-09-23 23:20发表 [回复] [引用][举报]
    很不错,这样就大致理解虚函数了
    10楼 kekey12102014-09-21 11:32发表 [回复] [引用][举报]
    9楼 superbin2014-09-19 15:04发表 [回复] [引用][举报]
    好牛的技术文章!
    8楼 hustcalm2014-09-14 11:35发表 [回复] [引用][举报]
    好文!
    不过还是觉得看《深入理解C++对象模型》是最靠谱的!
    7楼 yonggeno12014-09-14 10:06发表 [回复] [引用][举报]
    是一篇好文章,支持作者!
    6楼 lanshanwanghao2014-09-12 20:48发表 [回复] [引用][举报]
    楼主太牛逼了。讲的简单明了
    5楼 lyzsyr2014-08-27 10:57发表 [回复] [引用][举报]
    毕业两年了,终于要啃一啃c++了。
    楼主,写的很清楚很透彻。
    学习,膜拜了!
    4楼 寒山-居士2014-08-12 18:31发表 [回复] [引用][举报]
    写的很清晰,也很形象,就需要这样的 讲的很活,不像其他文章喜欢咬文嚼字
    3楼 u0116snail2014-08-12 11:17发表 [回复] [引用][举报]
    不错,谢谢楼主
    2楼 最怕认真2014-08-08 19:10发表 [回复] [引用][举报]
    很详细啊。我感觉一下子清晰了很多。
    1楼 whs20047892014-08-01 15:55发表 [回复] [引用][举报]
    [cpp] view plain copy
    print?
    1. typedef void(*Fun)(void);   //void类型的函数指针  
    2.   
    3. class Base   
    4. {  
    5. public:  
    6.     virtual void f() { cout << "Base::f" << endl; }  
    7.     virtual void g() { cout << "Base::g" << endl; }  
    8.     virtual void h() { cout << "Base::h" << endl; }  
    9. private:  
    10.     virtual void j() { cout << "Base::j" << endl;}  
    11. };  
    12.   
    13. class dev: public Base  
    14. {  
    15. public:  
    16.     virtual void k() { cout << "dev::k" << endl; }  
    17. };  
    18.   
    19. int main()  
    20. {  
    21.       
    22.     //Base b1;  
    23.     //b1.j();            //compile error  
    24.     dev d;  
    25.     //d.f();             //compile error  
    26.     //通过函数指针访问到私有的j(), j()对于对象来讲本来是不可见的,指针太强大  
    27.     Fun pFun2 = (Fun)*((int*)*(int*)(&d)+3);   
    28.     pFun2();  
    29.   
    30.     Base *b2 = new dev();  
    31.     //b2->k();           //compile error,父类指针无法call子类特有的虚函数  
    32.     //通过函数指针访问到子类特有的虚函数k(), 指针太强大  
    33.     Fun pFun3 = (Fun)*((int*)*(int*)b2+4);   
    34.     pFun3();  
    35.   
    36.     return 0;  
    37. }  
    typedef void(*Fun)(void);   //void类型的函数指针class Base {public:virtual void f() { cout << "Base::f" << endl; }virtual void g() { cout << "Base::g" << endl; }virtual void h() { cout << "Base::h" << endl; }private:virtual void j() { cout << "Base::j" << endl;}};class dev: public Base{public:virtual void k() { cout << "dev::k" << endl; }};int main(){        //Base b1;//b1.j();            //compile errordev d;//d.f();             //compile error//通过函数指针访问到私有的j(), j()对于对象来讲本来是不可见的,指针太强大    Fun pFun2 = (Fun)*((int*)*(int*)(&d)+3);     pFun2();Base *b2 = new dev();//b2->k();           //compile error,父类指针无法call子类特有的虚函数//通过函数指针访问到子类特有的虚函数k(), 指针太强大    Fun pFun3 = (Fun)*((int*)*(int*)b2+4);     pFun3();return 0;}
    查看更多评论
    发表评论
    • 用 户 名:
    • u010499172
    • 评论内容:
    • 插入代码
      HTML/XMLobjective-cDelphiRubyPHPC#C++JavaScriptVisual BasicPythonJavaCSSSQL其它
        
    * 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    快速回复TOP
    原创粉丝点击