C++中虚函数功能的实现机制

   要理解C++中虚函数是如何工作的,需要回答四个问题。

  1、 什么是虚函数。

  虚函数由于必须是在类中声明的函数,因此又称为虚方法。所有以virtual修饰符开始的成员函数都成为虚方法。此时注意是virtual修饰的成员函数不是virtual修饰的成员函数名。

  例如:基类中定义:

  virtual void show(); //由于有virtual修饰因此是虚函数

  voidshow(int); //虽然和前面声明的show虚函数同名,但不是虚函数。

  所有的虚函数地址都会放在所属类的虚函数表vtbl中。另外在基类中声明为虚函数的成员方法,到达子类时仍然是虚函数,即使子类中重新定义基类虚函数时未使用virtual修饰,该函数地址仍会放在子类的虚函数表vtbl中。

  2、 正确区分重载、重写和隐藏。

  注意三个概念的适用范围:处在同一个类中的函数才会出现重载。处在父类和子类中的函数才会出现重写和隐藏。

  重载:同一类中,函数名相同,但参数列表不同。

  重写:父子类中,函数名相同,参数列表相同,且有virtual修饰。

  隐藏:父子类中,函数名相同,参数列表相同,但没有virtual修饰;

  或:函数名相同,参数列表不同,无论有无virtual修饰都是隐藏。

  例如:

  基类中:(1) virtual void show(); //是虚函数

  (2) void show(int); //不是虚函数

  子类中:(3) void show(); //是虚函数

  (4) void show(int); //不是虚函数

  1,2构成重载,3,4构成重载,1,3构成重写,2,4构成隐藏。另外2,3也会构成隐藏,子类对象无法访问基类的void show(int)成员方法,但是由于子类中4的存在导致了子类对象也可以直接调用void show(int)函数,不过此时调用的函数不在是基类中定义的void show(int)函数2,而是子类中的与3重载的4号函数。

  3、 虚函数表是如何创建和继承的。

  基类的虚函数表的创建:首先在基类声明中找到所有的虚函数,按照其声明顺序,编码0,1,2,3,4……,然后按照此声明顺序为基类创建一个虚函数表,其内容就是指向这些虚函数的函数指针,按照虚函数声明的顺序将这些虚函数的地址填入虚函数表中。例如若show放在虚函数声明的第二位,则在虚函数表中也放在第二位。

  对于子类的虚函数表:首先将基类的虚函数表复制到该子类的虚函数表中。若子类重写了基类的虚函数show,则将子类的虚函数表中存放show的函数地址(未重写前存放的是子类的show虚函数的函数地址)更新为重写后函数的函数指针。若子类增加了一些虚函数的声明,则将这些虚函数的地址加到该类虚函数表的后面。

  4、 虚函数表是如何访问的。

  当执行pBase->show()时,要观察show在Base基类中声明的是虚函数还是非虚函数。若为虚函数将使用动态联编(使用虚函数表决定如何调用函数),若为非虚函数则使用静态联编(根据调用指针pBase的类型来确定调用哪个类的成员函数)。此处假设show为虚函数,首先:由于检查到pBase指针类型所指的类Base中show定义为虚函数,因此找到pBase所指的对象(有可能是Base类型也可能是Extend类型。),访问对象得到该对象所属类的虚函数表地址。其次:查找show在Base类中声明的位置在Base类中所有虚函数声明中的位序。然后到pBase所指对象的所属类(有可能是Extend哦,多态)的虚函数表中访问该位序的函数指针,从而得到要执行的函数。

  例如:

  基类Base::virtualvoid show(); (1)

  子类Extend::virtualvoid show(); (2)

  Externext;

  Base*pBase=&ext;

  pBase->show();

  当执行pBase->show();时首先到Base中查看show(),发现其为虚函数,然后访问pBase指向的ext对象,在对象中得到Extend类的虚函数表,在Base类声明中找到show()声明的位序0,访问Extend类的虚函数表的位置0,得到show的函数地址。注意若只有基类定义了virtual void show();而子类未重写virtual void show();即上面的函数(2),则Extend虚函数表中的位序0中存放的地址仍然是Base类中定义的virtual void show()函数,而若Extend类中重写了Base类中的virtual void show()方法,则Extend的虚函数表中位序0的函数地址将被更新为Extend中新重写的函数地址。从而调用pBase->show()时将产生多态的现象。

  总结:当调用pBase->show();时,执行的步骤:

  1, 判断Base类中show是否为虚函数。

  2, 若不是虚函数则找到pBase所指向的对象所属类Base。执行Base::show()。若是虚函数则执行步骤3.

  3, 访问pBase所指对象的虚函数表指针得到pBase所指对象所在类的虚函数表。

  4, 查找Base中show()在声明时的位序为x,到步骤3得到的虚函数表中找到位序3,从而得到要执行的show的函数地址。

  5, 根据函数地址和Base中声明的show的函数类型(形参和返回值)访问地址所指向的函数。

  以上为虚函数的工作机制。

  注意只有用virtual修饰的成员方法才会放到虚函数表中去。

  子类对父类函数的隐藏将导致无法通过子类对象访问基类的成员方法。

  因此给出以下建议:

  1、 若要在子类中重新定义父类的方法(有virtual为重写,无virtual为隐藏),则应确保子类中的函数声明和父类函数声明中的形参完全一样。但返回值类型是基类引用/指针的成员函数在重新定义时可以返回子类的引用/指针(返回值协变),这是由于子类的对象可以赋给基类引用/指针。

  2、 若基类中声明了函数的重载版本,则在派生类中重新定义时应该重新定义所有基类的重载版本。这是因为,重新定义一个函数,其他的基类重载版本将被隐藏,导致子类无法使用这些基类的成员方法。所以需要每个都重新定义。若一些父类的重载版本,子类确实不需要修改,则由于重新定义了一个重载版本,即使有些重载版本不需要修改也要重新定义,在定义体中直接调用基类的成员方法(使用作用于限定符访问)。

  3、 从虚函数的实现机制可以看到要想在子类中实现多态需要满足三个重要的条件。(1)在基类中函数声明为虚函数。(2)在子类中,对基类的虚函数进行了重写。(3)基类的指针指向了子类的对象

时间: 2024-10-24 04:40:45

C++中虚函数功能的实现机制的相关文章

C++中虚函数与纯虚函数的用法_C 语言

本文较为深入的分析了C++中虚函数与纯虚函数的用法,对于学习和掌握面向对象程序设计来说是至关重要的.具体内容如下: 首先,面向对象程序设计(object-oriented programming)的核心思想是数据抽象.继承.动态绑定.通过数据抽象,可以使类的接口与实现分离,使用继承,可以更容易地定义与其他类相似但不完全相同的新类,使用动态绑定,可以在一定程度上忽略相似类的区别,而以统一的方式使用它们的对象. 虚函数的作用是实现多态性(Polymorphism),多态性是将接口与实现进行分离,采用

sql-oracle实现excel中norminv函数功能

问题描述 oracle实现excel中norminv函数功能 如何运用sql实现excel中的norminv函数的功能,求指教 解决方案 在Sharepoint中实现Excel中的NetWorkDays函数功能JAVA 实现下载功能 保存为EXCEL格式自定义Oracle 函数,传入参数,实现简单的功能 解决方案二: http://office.wps.cn/officeexcel/18220-2013-04-10-16-33-40-582.html

helper_string.h中checkCmdLineFlag函数功能以及参数含义

问题描述 helper_string.h中checkCmdLineFlag函数功能以及参数含义 vs中cuda编程时,代码中碰到checkCmdLineFlag(argc, (const char **)argv, "help"),不知道含义,请各位大侠解答. 解决方案 参数就是三个参数,arge和argv代表参数个数和一个带有命令行字符串的二级指针,第三个参数是你要找的字符串(比如"help","device") inline bool che

php实现excel中rank函数功能的方法_php技巧

本文实例讲述了php实现excel中rank函数功能的方法.分享给大家供大家参考.具体分析如下: sql语句实现排名是像这样的如: 总分成绩为 195,180,180,161,名次分别为1,2,3,4,遇到并列的情况也是按照顺序的, 而Excel函数rank排名得到的结果是1,2,2,4,遇到并列跳过中间的3 下面的函数模拟的就是这种情况 函数如下(不知道有没有更好的实现方法): 公式为: 名次=总人数--比自己小的数的个数-这个分数重复次数+1(加上自己) 得到名次的数组再根据对应的id写入到

浅谈C++中虚函数实现原理揭秘_C 语言

编译器到底做了什么实现的虚函数的晚绑定呢?我们来探个究竟.      编译器对每个包含虚函数的类创建一个表(称为V TA B L E).在V TA B L E中,编译器放置特定类的虚函数地址.在每个带有虚函数的类 中,编译器秘密地置一指针,称为v p o i n t e r(缩写为V P T R),指向这个对象的V TA B L E.通过基类指针做虚函数调 用时(也就是做多态调用时),编译器静态地插入取得这个V P T R,并在V TA B L E表中查找函数地址的代码,这样就能调用正确的函数使

PHP5中虚函数的实现方法分享_php技巧

请看下面的代码: 复制代码 代码如下: <?php class A { public function x() { echo "A::x() was called.\n"; } public function y() { self::x(); echo "A::y() was called.\n"; } public function z() { $this->x(); echo "A::z() was called.\n"; } }

C#中虚函数,抽象,接口的简单说明

函数 虚函数:由virtual声明,它允许在派生类中被重写,要重写方法,必须先声名为virtual public class myclass { public virtual int myint() { 函数体: } } class myclass1:myclass { public override int myint() { 函数体1: } } 抽象类.抽象函数:由abstract声明,在抽象类中可以定义抽象方法,抽象方法基本没有执行代码,派生类必须重写它,提供其执行代码 public ab

c#中虚函数的相关使用方法_C#教程

若一个实例方法声明前带有virtual关键字,那么这个方法就是虚方法. 虚方法与非虚方法的最大不同是,虚方法的实现可以由派生类所取代,这种取代是通过方法的重写实现的(以后再讲)虚方法的特点:虚方法前不允许有static,abstract,或override修饰符虚方法不能是私有的,因此不能使用private修饰符虚方法的执行:我们知道一般函数在编译时就静态地编译到了执行文件中,其相对地址在程序运行期间是不发生变化的,而虚函数在编译期间是不被静态编译的,它的相对地址是不确定的,它会根据运行时期对象

c++中虚函数和纯虚函数的作用与区别_C 语言

虚函数为了重载和多态的需要,在基类中是有定义的,即便定义是空,所以子类中可以重写也可以不写基类中的此函数! 纯虚函数在基类中是没有定义的,必须在子类中加以实现,很像java中的接口函数! 虚函数 引入原因:为了方便使用多态特性,我们常常需要在基类中定义虚函数. class Cman { public: virtual void Eat(){--}; void Move(); private: }; class CChild : public CMan { public: virtual void