第一个文件:
多态性底层实现机制知识点.cpp
//此页面可以通过在dev_c++4.9.9.2运行,并通过小量的更改在其他IDE下运行. #if 0 Copyright (c) 2009,万维网 All rights reserved. 文件名称:memSet.h 摘 要:本文描述c++多态性的实现机制以及virtual在内存中的分配情况. 备 注: 本文资料收集于网络并通过作者整理. 当前版本:1.0 作 者:张旭 完成日期:2009年8月28日 #endif #if 0 【多态性】 多态是面向对象程序设计的重要特征之一, 是扩展性在"继承"之后的又一重大表现. 关于多态, 简而言之就是用父类型别的指针指向其子类的实例, 然后通过父类的指针调 用实际子类的成员函数. 这种技术可以让父类的指针有"多种形态", 这是一种泛型技术. 所谓泛型技术, 说白了就是试图使用不变的代码来实现可变的算法. 比如: 模板技术, RTTI技术, 虚函数技术, 要么是试图做到在编译时决议, 要么试图做到运行时决议. 【虚函数virtual】 被virtual关键字修饰的成员函数, 就是虚函数. 虚函数的作用, 用专业术语来解释就 是实现多态性{ Polymorphism }, 多态性是将接口与实现进行分离; 用形象的语言来解 释就是实现以共同的方法, 但因个体差异而采用不同的策略. 【虚函数virtual funtion的实现机制】 对C++了解的人都应该知道虚函数{ Virtual Function }是通过一张虚函数表{Virtual Table} 来实现的. 简称为V-Table. 在这个表中, 主是要一个类的虚函数的地址表, 这张表解决了 继承, 重载的问题, 保证其能真实反应实际的函数. #if 0 下面观点有点问题【个人偏向二】 1:一个类的虚函数表是静态的, 也就是说对这个类的每个实例而言, 它的虚函数表的是 固定的, 不会为每个实例生成一个相应的虚函数表{ 只是在每个带有虚函数的类初始化 的时候, 把虚表的指针放入实例的地址首位 } 2:无论何时当实例化具有一个或者多个虚函数的类的对象时, 编译器会给这个对象附上 指针, 指向对象所属类的Virtual函数表. { 每个类都有一个虚函数表 }, 如果在派生类 中对基类的Virtual函数进行覆盖{不称为重载}的话, 则派生类中的虚函数表把重载的虚 函数把存在虚函数表中的该函数的地址覆盖掉. #endif 所以, 当我们用父类的指针来操作一个子类的时候, 这张虚函数表就显得由为重要了, 它就像一个地图一样, 指明了实际所应该调用的函数. 在C++的标准规格说明书中说到, 编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置{这是为了保证正确取 到虚函数的偏移量 }. 这意味着我们通过对象实例的地址得到这张虚函数表, 然后就可 以遍历其中函数指针, 并调用相应的函数. 例子详见:例子程序 【使用虚函数应该注意的东西】 1:虚函数必须是基类的非静态成员函数, 其访问权限可以是protected或public, 在基 类的类定义中定义虚函数的一般形式: virtual 函数返回值类型 虚函数名(形参表) 2:如果虚函数在基类与派生类中出现, 仅仅是名字相同, 而形式参数不同, 或者是返 回类型不同,那么即使加上了virtual关键字, 也是不会进行滞后编联{ 也就是迟绑定 }的. 3:只有类的成员函数才能说明为虚函数, 因为虚函数仅适合用与有继承关系的类对象, 所以普通函数不能说明为虚函数. 静态成员函数不能是虚函数, 因为静态成员函数的特点是不受限制于某个对象, 而是 为类的所有实例所共享. 4:构造函数不能是虚函数. why? 5:内联(inline)函数不能是虚函数, 因为内联函数不能在运行中动态确定位置. 即使虚 函数在类的内部定义定义{ 普通情况下,在函数类内部定义的函数编译器缺省为内联函数 }, 但是在编译的时候系统仍然将它看做是非内联的. 6:析构函数可以是虚函数, 而且通常声明为虚函数. 实际上, 优秀的程序员常常把基类 的析构函数定义为虚函数. 因为, 将基类的析构函数定义为虚函数后, 当利用delete 删除一个指向派生类定义的对象指针时, 系统会调用相应的类的析构函数. 而不将析 构函数定义为虚函数时, 只调用基类的析构函数.{ 而其即使是名字不同, 也会进行滞后编联 } 这一点是第2点的特例. 7:基类只能通过指针调用虚函数, 引用也可以. 如果用成员选择符则是错误的. 因为用 成员选择符的话, 用哪个函数编译在编译时就已经决定好了{ 静态绑定 },而不是在 向迟绑定在运行时决定的. 8:只需要在声明函数的类体中使用关键字"virtual"将函数声明为虚函数, 而定义函数 时不需要使用关键字"virtual". 【关于虚表指针的位置】 只要在一个类中声明了一个虚函数或者多个虚函数, 该类在实例化的时候, 总是隐式地生成 一个且仅一个指向virtual table的指针vfptr, 一个或者多个该类虚函数的指针将被存放在 virtual table中. 另外, 如果一个类存在虚函数, 那么vfptr总是在最前面的, 即在所有成 员变量之前. "如例子中的程序结果所示." 【关于overload和override】 虚函数总是在派生类中被改写, 这种改写被称为"override". "override" 是指派生类重写基类的虚函数, 就象我们前面B类中重写了A类中的foo()函数. 重写 的函数必须有一致的参数表和返回值. 这个单词好象一直没有什么合适的中文词汇来对应, 有人译为"覆盖". "overload" 约定成俗的被翻译为"重载". 是指编写一个与已有函数同名但是参数表不同的函数. 例如一个函数即可以接受整型数作为参数, 也可以接受浮点数作为参数. #endif
第二个文件:
virtual.h
#ifndef VIRTUAL_H_ #define VIRTUAL_H_ #include
第三个文件:
textVirtual.cpp
#include