中心提示:虚函数必须是类的非静态成员函数(且非构造函数), 其访问权限是public(能够定义为privateorproteceted, 可是关于多态来讲, 没有意义。 在基类的类定义中定义虚函数的普通形式 虚函数的定义: 虚函数必须是类的非静态成员函数(且非构造函数), 可是关于多态来讲, 没有意义。 ), 在基类的类定义中定义虚函数的普通形式: virtual函数返回值类型虚函数名(形参表) 虚函数的做用是完成静态联编, 在定义了虚函数后, 在派生类中从新定义的函数应与虚函数具备相同的形参个数和形参类型。 以完成统一的接口, 不一样定义过程。 若是在派生类中没有对虚函数从新定义, 则它承继其基类的虚函数。 当顺序发现虚函数名前的关键字virtual后, 会自动将其做为静态联编处理, 即在顺序运转时静态地选择合适的成员函数。 二、类之间存在子类型关系, 普通表现为一个类从另外一个类私有派生而来。 定义虚函数的限制: ??)非类的成员函数不能定义为虚函数, 类的成员函数中静态成员函数和构造函数也不能定义为虚函数, 但能够将析构函数定义为虚函数。 优秀的顺序员常常把基类的析构函数定义为虚函数。 由于, 将基类的析构函数定义为虚函数后, 系统会调用相应的类的析构函数。 而不将析构函数定义为虚函数时, ?而定义函数时不需求使用关键字“virtual”。 ?则在该类中不能出现和这个成员函数同名而且返回值、参数个数、参数类型都相同的非虚函数。 也不能出现这种非虚的同名同返回值同参数个数同参数类型函数。 为何虚函数必须是类的成员函数: 虚函数诞生的目的就是为了完成多态, 在类外定义虚函数毫无实际用途。 为何类的静态成员函数不能为虚函数: 若是定义为虚函数, 那么它就是静态绑定的, 为何构造函数不能为虚函数: 由于若是构造函数为虚函数的话, 它将在执行期间被构造, 而执行期则需求对象已经树立, 构造函数所完成的任务就是为了树立合适的对象, 在承继体系中, 构造的顺序就是从基类到派生类, 其目的就在于确保对象可以成功地构建。 构造函数同时承当着虚函数表的树立, 如何确保vtbl的构建成功呢? 留意:当基类的构造函数外部有虚函数时, 又如何任务呢?与构造函数相同, 只要“局部”的版本被调用。 行为相同, 构造函数只能调用“局部”版本, 咱们知道, 析构函数的调用顺序与构造函数相反, 当某个类的析构函数被调用时, 其派生类的析构函数已经被调用了, 相应的数据也已被丢失, 若是再调用虚函数的派生类的版本, 就至关于对一些不牢靠的数据中止操做, 这是十分危险的。 所以, 虚函数机制也是不起做用的。 C++中的虚函数的做用主要是完成了多态的机制。 关于多态, 简而言之就是用父类型别的指针指向其子类的实例, 而后通过父类的指针调用实际子类的成员函数。 这种技术可让父类的指针有“多种外形”, 这是一种泛型技术。 虚函数技术, 关于虚函数的使用方法, 我在这里不作过多的论述。 你们能够看看相关的 C++的书籍。 在这篇文章中, 固然, 相同的文章在网上也出现过一些了, 没有比拟, 没有触类旁通。 也但愿你们多给我提意见。 让咱们一同进入虚函数的世界。 虚函数表 对C++了解的人都应该知道虚函数(VirtualFunction)是通过一张虚函数表(VirtualTable)来完成的。 主是要一个类的虚函数的地址表, )中这个表被分配在了这个实例的内存中(注:一个类的虚函数表是静态的, 他的虚函数表的是固定的, 不会为每一个实例生成一个相应的虚函数表。 因此, 当咱们用父类的指针来操做一个子类的时分, 这张虚函数表就显得由为重要了, 它就像一个地图同样, 假定咱们有这样的一个类: 依照下面的说法, 咱们能够通过Base的实例来获得Base的虚函数表。 咱们能够通过强行把&b转成int, 取得虚函数表的地址, 而后, 也就是Base::f(), 这在下面的顺序中获得了验证(把int强迫转成了函数指针)。 通过这个示例, 以下所示: 留意:在下面这个图中, 我在虚函数表的最后多加了一个结点, 这是虚函数表的完毕结点, 就像字符串的完毕符“\0”同样, 其标志了虚函数表的完毕。 这个完毕标志的值在不一样的编译器下是不一样的。 我将区分说明“无覆盖”和“有覆盖”时的子类虚函数表的样子。 我之因此要讲述没有覆盖的状况, 主要目的是为了给一个对比。 普通承继(无虚函数覆盖) 下面, 再让咱们来看看承继时的虚函数表是什么样的。 假定有以下所示的一个承继关系: 请留意, 在这个承继关系中, 子类没有重写任何父类的函数。 重载就是所谓的名同而签名不一样, 重写就是对子类对虚函数的从新完成。 ) 咱们能够看到下面几点: 1)虚函数依照其声明顺序放于表中。 普通承继(有虚函数覆盖) 覆盖父类的虚函数是很显然的事情, 否则, 若是子类中有虚函数重载了父类的虚函数, 为了让你们看到被承继事后的效果, 那么, 关于派生类的实例的虚函数表会是下面的样子: 咱们从表中能够看到下面几点, 2)没有被覆盖的函数照旧。 由b所指的内存中的虚函数表(子类的虚函数表)的f()的位置已经被Derive::f()函数地址所取代, 因而在实际调用发做时, 这就完成了多态。 再让咱们来看看多重承继中的状况, 关于子类实例中的虚函数表, 2)子类的成员函数被放到了第一个父类的表中。 而可以调用到实际的函数。 多重承继(有虚函数覆盖) 下面咱们再来看看, 咱们能够看见, 三个父类虚函数表中的f()的位置被交流成了子类的函数指针。 咱们就能够用任一个父类指针来指向子类, 并调用子类的f()了。 如: 安全性 每次写C++的文章, 这篇文章也不例外。 相信咱们对虚函数表有一个比拟细致的了解了。 水可载舟, 亦可覆舟。 下面, 让咱们来看看咱们能够用虚函数表来干点什么好事吧。 子类没有重载父类的虚函数是一件毫无心义的事情。 由于多态也是要基于函数重载的。 虽然在下面的图中咱们能够看到子类的虚表中有Derive本人的虚函数, 因此, 这样的顺序根本没法编译通过。 但在运转时, 但这些非public的虚函数一样会存在于子类虚函数表中, 如: 完毕语 C++这门语言是一门Magic的语言, 关于顺序员来讲, 咱们彷佛永远摸不清楚这门语言背着咱们在干了什么。 需求熟悉这门语言, 需求去了解C++中那些危险的东西。 否则, 这是一种搬起石头砸本人脚的编程语言。编程