C++虚函数及虚函数表解析

  中心提示:虚函数必需是类的非静态成员函数(且非结构函数), 其拜访权限是public(可以定义为privateorproteceted, 但是关于多态来说, 没有意义。 ), 在基类的类定义中定义虚函数的普通形式  虚函数的定义:  虚函数必需是类的非静态成员函数(且非结构函数), 其拜访权限是public(可以定义为privateorproteceted, 没有意义。 也就是在顺序的运转阶段静态地选择适宜的成员函数, 在定义了虚函数后, 在派生类中重新定义的函数应与虚函数具有相反的形参个数和形参类型。 如果在派生类中没有对虚函数重新定义, 则它承袭其基类的虚函数。 当顺序发现虚函数名前的关键字virtual后, 会自动将其作为静态联编处理, 即在顺序运转时静态地选择适宜的成员函数。   2、类之间存在子类型关系, 普通表现为一个类从另一个类私有派生而来。 然后直接或者直接运用基类指针调用虚函数。 ?)非类的成员函数不能定义为虚函数, 类的成员函数中静态成员函数和结构函数也不能定义为虚函数, 实践上, 系统会调用相应的类的析构函数。 只调用基类的析构函数。 ?)只需要在声明函数的类体中运用关键字“virtual”将函数声明为虚函数, 而定义函数时不需要运用关键字“virtual”。 则在该类中不能呈现和这个成员函数同名并且返回值、参数个数、参数类型都相反的非虚函数。 在以该类为基类的派生类中, 也不能呈现这种非虚的同名同返回值同参数个数同参数类型函数。   为什么虚函数必需是类的成员函数:  虚函数诞生的目的就是为了完成多态, 在类外定义虚函数毫无实践用途。 那么它就是静态绑定的, 也就是在派生类中可以被覆盖的, 这与静态成员函数的定义(:在内存中只要一份拷贝;通过类名或对象引用拜访静态成员)本身就是相矛盾的。   为什么结构函数不能为虚函数:  因为如果结构函数为虚函数的话, 它将在执行期间被结构, 而执行期则需要对象曾经树立, 结构函数所完成的任务就是为了树立适宜的对象, 在承袭体系中, 结构的顺序就是从基类到派生类, 其目的就在于确保对象可以成功地构建。 如果它本身都是虚函数的话, 如何确保vtbl的构建成功呢?  注意:当基类的结构函数内部有虚函数时, 会呈现什么状况呢?结果是在结构函数中, 只要“局部”的版本被调用。 析构函数则是因为派生类版本的信息曾经不可靠了。 我们知道, 析构函数的调用顺序与结构函数相反, 是从派生类的析构函数到基类的析构函数。 其派生类的析构函数曾经被调用了, 如果再调用虚函数的派生类的版本, 就相当于对一些不可靠的数据停止操作, 因此, 虚函数机制也是不起作用的。 关于多态, 简而言之就是用父类型别的指针指向其子类的实例, 这种技术可以让父类的指针有“多种外形”, 这是一种泛型技术。 说白了就是试图运用不变的代码(Or不变的接口)来完成可变的算法。 比如:模板技术, RTTI技术, 虚函数技术, 要么是试图做到在编译时决议, 大家可以看看相关的  C++的书籍。 在这篇文章中, 我只想从虚函数的完成机制下面为大家一个明晰的分析。   当然, 但我总感觉这些文章不是很容易阅读, 大段大段的代码, 没有图片, 没有详细的说明, 没有比较, 所以这是我想写下这篇文章的原因。 也希望大家多给我提意见。 让我们一同进入虚函数的世界。   虚函数表  对C++了解的人都应该知道虚函数(VirtualFunction)是通过一张虚函数表(VirtualTable)来完成的。 简称为V-Table。 主是要一个类的虚函数的地址表, 这张表解决了承袭、覆盖的问题, )中这个表被分配在了这个实例的内存中(注:一个类的虚函数表是静态的, 他的虚函数表的是固定的, 不会为每个实例生成一个相应的虚函数表。 ), 所以, 这张虚函数表就显得由为重要了, 它就像一个地图一样, 指明了实践所应该调用的函数。   这里我们着重看一下这张虚函数表。 在C++的标准规格说明书中说到, 编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量)。 这意味着我们通过对象实例的地址得到这张虚函数表, 并调用相应的函数。   假定我们有这样的一个类:  依照下面的说法, 我们可以看到, 再次取址就可以得到第一个虚函数的地址了, 也就是Base::f(), 这在下面的顺序中得到了验证(把int强制转成了函数指针)。 通过这个示例, 其代码如下:  画个图解释一下。 如下所示:  注意:在下面这个图中, 就像字符串的完毕符“\0”一样, 这个完毕标志的值在不同的编译器下是不同的。 没有覆盖父类的虚函数是毫无意义的。 我之所以要讲述没有覆盖的状况, 在比较之下, 我们可以更加清楚地知道其内部的具体完成。   普通承袭(无虚函数覆盖)  下面, 再让我们来看看承袭时的虚函数表是什么样的。 假定有如下所示的一个承袭关系:  请注意, 子类没有重写任何父类的函数。 那么, 重写就是对子类对虚函数的重新完成。 )  我们可以看到下面几点:  1)虚函数依照其声明顺序放于表中。 不然, 下面,   为了让大家看到被承袭事先的效果, 在这个类的设计中, 我只覆盖了父类的一个函数:f()。 那么, 关于派生类的实例的虚函数表会是下面的样子:  我们从表中可以看到下面几点,   1)覆盖的f()函数被放到了子类虚函数表中原来父类虚函数的位置。   2)没有被覆盖的函数照旧。   由b所指的内存中的虚函数表(子类的虚函数表)的f()的位置曾经被Derive::f()函数地址所取代, 于是在实践调用发作时, 是Derive::f()被调用了。 这就完成了多态。 假定有下面这样一个类的承袭关系。 是下面这个样子:  我们可以看到:  1)每个父类都有自己的虚表。   2)子类的成员函数被放到了第一个父类的表中。 (所谓的第C++一个父类是依照声明顺序来判别的)  这样做就是为了解决不同的父类类型的指针指向同一个子类实例, 而可以调用到实践的函数。   多重承袭(有虚函数覆盖)  下面我们再来看看,   我们可以看见, 这样, 如:  平安性  每次写C++的文章, 总免不了要批判一下C++。 水可载舟, 子类没有重载父类的虚函数是一件毫无意义的事情。 虽然在下面的图中我们可以看到子类的虚表中有Derive自己的虚函数, 但我们基本不可能运用基类的指针来调用子类的自有虚函数:  任何妄图运用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为合法, 所以, 如果父类的虚函数是private或是protected的,   如:  完毕语  C++这门言语是一门Magic的言语, 关于顺序员来说, 需要熟悉这门言语, 我们就必需要了解C++外面的那些东西,

posted on 2011-03-30 19:36  jiyizhen3721  阅读(149)  评论(0编辑  收藏  举报