C++虚函数原理,c虚函数详解你肯定懂了

  C++虚函数原理,c虚函数详解你肯定懂了

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

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

  当然,网上也出现过一些相同的文章,但我总觉得这些文章不好读。有大段大段的代码,没有图片,没有详细的解释,没有对比,没有推论。不利于学习和阅读,所以这是我想写这篇文章的原因。也希望大家多提意见。

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

  懂C的人应该知道,VirtualFunction是通过一个VirtualTable实现的。简称v表。在这个表中,需要一个类的虚函数的地址表。该表解决了继承性和覆盖性问题,保证了能真实反映实际功能。这样,在具有虚函数的类的实例中,这个表被分配在这个实例的内存中。所以当我们用父类的指针去操作子类的时候,这个虚函数表就变得重要了。像地图一样,它指出了实际应该调用的函数。

  让我们来关注一下这个虚函数表。根据C的标准规范,编译器必须保证虚函数表的指针存在于对象实例的前端位置(这是为了保证虚函数的偏移量正确)。这意味着我们通过对象实例的地址得到这个虚函数表,然后就可以遍历其中的函数指针,调用相应的函数。

  听了这么多,我能感觉到你现在可能比以前更迷茫了。没关系,下面是实际例子。相信聪明的你一眼就明白了。

  假设我们有这样一个类:

  类别库{

  公共:

  virtualvoidf(){ cout Base:f endl;}

  virtualvoidg(){ cout Base:g endl;}

  virtualvoidh(){ cout Base:h endl;}

  };

  根据上面的陈述,我们可以通过Base的实例得到虚函数表。下面是实际的例程:

  typedefvoid(* Fun)(void);

  Baseb

  FunpFun=NULL

  Cout 虚函数表地址:“(int *)(b)endl;

  Cout 虚函数表—第一个函数地址:“(int *)*(int *)(b)endl;

  //Invokethefirstvirtualfunction

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

  pFun();

  实际运行结果如下:(Windows XP VS 2003,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。在Ubuntu7.10 Linux2.6.22 GCC4.1.3下,如果该值为1,则表示有下一个虚函数表,如果该值为0,则表示是最后一个虚函数表。

  接下来,我将分别解释虚函数表在“未覆盖”和“覆盖”时的外观。拥有一个不覆盖父类的虚函数是没有意义的。我之所以要讲没有覆盖的情况,主要是为了做一个对比。通过对比,我们可以更清楚的知道它的内部实现。

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

  接下来,我们来看看虚函数表在继承时是什么样子的。假设存在如下所示的继承关系:

  请注意,在这个继承关系中,子类不会重载父类的任何函数。那么,在派生类的例子中,它的虚函数表如下:

  比如:派生的;的虚函数表如下:

  我们可以看到以下几点:

  1)虚函数按照它们的声明顺序放在表中。

  2)父类的虚函数先于子类的虚函数。

  我相信聪明的你可以参考前面的程序写个程序验证一下。

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

  覆盖父类的虚函数是显而易见的,否则虚函数就变得没有意义。现在,让我们来看看。如果子类中的虚函数重载了父类的虚函数,会是什么样子?假设我们有如下继承关系。

  为了让大家看到继承的效果,在这个类的设计中,我只覆盖了父类的一个函数:f()。然后,对于派生类的实例,其虚函数表将如下所示:

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

  1)被覆盖的f()函数被放置在虚拟表中原始父虚函数的位置。

  2)未涵盖的功能保持不变。

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

  base * b=new derive();

  b-

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

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

  接下来,我们来看看多重继承中的情况,假设有下面这个类的继承关系。注意:子类不会覆盖父类的功能。

  对于子类实例中的虚函数表,它看起来像这样:

  我们可以看到:

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

  2)子类的成员函数放在第一个父类的表中。(所谓第一个父类是根据声明顺序判断的)

  这是为了解决不同父类型的指针指向同一个子类实例的问题,可以调用实际的函数。

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

  我们再来看看,如果虚函数覆盖发生了。

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

  以下是子类实例中虚函数表的示意图:

  我们可以看到f()在三个父类的虚函数表中的位置被子类的函数指针所取代。这样,我们可以指向任何静态类型的子类,并调用子类的f()。比如:

  派生的;

  Base1*b1=

  Base2*b2=

  Base3*b3=

  b1- //Derive:f()

  b2- //Derive:f()

  b3- //Derive:f()

  b1- //Base1:g()

  b2- //Base2:g()

  b3- //Base3:g()

  每次写关于C的文章,总要批评C,这篇文章也不例外。通过上面的描述,相信大家对虚函数表有了更详细的了解。水能载舟,亦能覆舟。现在,让我们看看虚函数表能做什么坏事。

  1.通过父类型的指针访问子类的虚函数。

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

  base 1 * B1=new derive();

  B1-f1();//编译错误

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

  第二,访问非公共的虚拟功能

  另外,如果父类的虚函数是私有的或者是受保护的,但是这些非公有的虚函数也会存在于虚函数表中,那么我们也可以通过访问虚函数表来访问这些非公有的虚函数,这很容易做到。

  比如:

  类别库{

  私人:

  virtualvoidf(){ cout Base:f endl;}

  };

  classDerive:publicBase{

  };

  typedefvoid(* Fun)(void);

  voidmain(){

  派生的;

  funp Fun=(Fun)*((int *)*(int *)(d)0);

  pFun();

  }

  c语言是一种神奇的语言。对于程序员来说,我们似乎永远不知道这种语言在背后做什么。要熟悉这门语言,我们需要知道C里面的东西,知道C里面的危险的东西,否则就是搬起石头砸自己的脚的编程语言。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

相关文章阅读

  • office2010激活密钥大全 怎么永久激活office2010
  • project2010产品密钥免费_project2010激活密钥永久激活码
  • c语言调用退出函数 c语言退出整个程序怎么写
  • c语言中怎么给函数初始化 c语言的初始化语句
  • c语言编写函数计算平均值 c语言求平均函数
  • chatgpt是什么?为什么这么火?
  • ChatGPT为什么注册不了?OpenAI ChatGPT的账号哪里可以注册?
  • OpenAI ChatGPT怎么注册账号?ChatGPT账号注册教程
  • chatgpt什么意思,什么是ChatGPT ?
  • CAD中怎么复制图形标注尺寸不变,CAD中怎么复制图形线性不变
  • cad中怎么创建并使用脚本文件,cad怎么运行脚本
  • cad中快速计算器的功能,cad怎么快速计算
  • cad中快速修改单位的方法有哪些,cad中快速修改单位的方法是
  • cad中心点画椭圆怎么做,cad轴测图怎么画椭圆
  • CAD中常用的快捷键,cad各种快捷键的用法
  • 留言与评论(共有 条评论)
       
    验证码: