多态(Polymorphism)的实现机制_第1页
多态(Polymorphism)的实现机制_第2页
多态(Polymorphism)的实现机制_第3页
多态(Polymorphism)的实现机制_第4页
多态(Polymorphism)的实现机制_第5页
已阅读5页,还剩2页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

多态 Polymorphism 的实现机制 txt 爱情是艺术 结婚是技术 离婚是算术 这年头女孩 们都在争做小 腰 精 谁还稀罕小 腹 婆呀 高职不如高薪 高薪不如高寿 高寿不如 高兴 多态 Polymorphism 是面向对象的核心概念 本文以 C 为例 讨论多态的具体实现 C 中多态可以分为基于继承和虚函数的动态多态以及基于模板的静态多态 如果没有特别 指明 本文中出现的多态都是指前者 也就是基于继承和虚函数的动态多态 至于什么是多 态 在面向对象中如何使用多态 使用多态的好处等等问题 如果大家感兴趣的话 可以找 本面向对象的书来看看 为了方便说明 下面举一个简单的使用多态的例子 From 1 class Shape protected int m x X coordinate int m y Y coordinate public Pure virtual function for drawing virtual void Draw 0 A regular virtual function virtual void MoveTo int newX int newY Regular method not overridable void Erase Constructor for Shape Shape int x int y Virtual destructor for Shape virtual Shape Circle class declaration class Circle public Shape private int m radius Radius of the circle public Override to draw a circle virtual void Draw Constructor for Circle Circle int x int y int radius Destructor for Circle virtual Circle Shape constructor implementation Shape Shape int x int y m x x m y y Shape destructor implementation Shape Shape Circle constructor implementation Circle Circle int x int y int radius Shape x y m radius radius Circle destructor implementation Circle Circle Circle override of the pure virtual Draw method void Circle Draw glib draw circle m x m y m radius main Define a circle with a center at 50 100 and a radius of 25 Shape pShape new Circle 50 100 25 Define a circle with a center at 5 5 and a radius of 2 Circle aCircle 5 5 2 Various operations on a Circle via a Shape pointer Polymorphism pShape Draw pShape MoveTo 100 100 pShape Erase delete pShape Invoking the Draw method directly aCircle Draw 例子中使用到多态的代码以黑体标出了 它们一个很明显的特征就是通过一个基类的 指针 或者引用 来调用不同子类的方法 那么 现在的问题是 这个功能是怎样实现的呢 我们可以先来大概猜测一下 对于 一般的方法调用 到了汇编代码这一层次的时候 一般都是使用 Call funcaddr 这样的指 令进行调用 其中 funcaddr 是要调用函数的地址 按理来说 当我使用指针 pShape 来调用 Draw 的时候 编译器应该将 Shape Draw 的地址赋给 funcaddr 然后 Call 指令就可以直 接调用 Shape Draw 了 这就跟用 pShape 来调用 Shape Erase 一样 但是 运行结果却告 诉我们 编译器赋给 funcaddr 的值却是 Circle Drawde 的值 这就说明 编译器在对待 Draw 方法和 Erase 方法时使用了双重标准 那么究竟是谁有这么大的法力 使编译器这个 铁面无私的判官都要另眼相看呢 virtual Clever 正是 virtual 这个关键字一手导演了这一出 乾坤大挪移 的好戏 说道 这里 我们先要明确两个概念 静态绑定和动态绑定 1 静态绑定 static bingding 也叫早期绑定 简单来说就是编译器在编译期间就 明确知道所要调用的方法 并将该方法的地址赋给了 Call 指令的 funcaddr 因此 运行期 间直接使用 Call 指令就可调用到相应的方法 2 动态绑定 dynamic binding 也叫晚期绑定 与静态绑定不同 在编译期间 编 译器并不能明确知道究竟要调用的是哪一个方法 而这 要知道运行期间使用的具体是哪个 对象才能决定 好了 有了这两个概念以后 我们就可以说 virtual 的作用就是告诉编译器 我要进 行动态绑定 编译器当然会尊重你的意见 而且为了完成你这个要求 编译器还要做很多的 事情 编译器自动在声明了 virtual 方法的类中插入一个指针 vptr 和一个数据结构 VTable vptr 用以指向 VTable VTable 是一个指针数组 里面存放着函数的地址 并保 证二者遵守下面的规则 1 VTable 中只能存放声明为 virtual 的方法 其它方法不能存放在里面 在上面的例 子中 Shape 的 VTable 中就只有 Draw MoveTo 和 Shape 方法 Erase 的地址并不能存放在 VTable 中 此外 如果方法是纯虚函数 如 Draw 那么同样要在 VTable 中保留相应的位 置 但是由于纯虚函数没有函数体 因此该位置中并不存放 Draw 的地址 而是可以选择存 放一个出错处理的函数的地址 当该位置被意外调用时 可以用出错函数进行相应的处理 2 派生类的 VTalbe 中记录的从基类中继承下来的虚函数地址的索引号必须跟该虚函数 在基类 VTable 中的索引号保持一致 如在上例中 如果在 Shape 的 VTalbe 中 Draw 为 1 号 MoveTo 2 号 Shape 为 3 号 那么 不管这些方法在 Circle 中是按照什么顺序定 义的 Circle 的 VTable 中都必须保证 Draw 为 1 号 MoveTo 为 2 号 至于 3 号 这里是 Circle 为什么不是 Shape 啊 嘿嘿 忘啦 析构函数不会继承的 3 vptr 是由编译器自动插入生成的 因此编译器必须负责为其进行初始化 初始化的 时间选在对象创建时 而地点就在构造函数中 因此 编译器必须保证每个类至少有一个构 造函数 若没有 自动为其生成一个默认构造函数 4 vptr 通常放在对象的起始处 也就是 Addr obj Addr obj vptr 你看 天下果然没有免费的午餐 为了实现动态绑定 编译器要为我们默默干了这么多 的脏话累活 如果你想体验一下编译器的辛劳 那么可以尝试用 C 语言模拟一下上面的行为 1 中就有这么一个例子 好了 现在万事具备 只欠东风了 编译 连接 载入 GO 当程序执行到 pShape Draw 的时候 上面的设施也开始起作用了 前面已经提到 晚期绑定时之所以不能确定调用哪个函数 是因为具体的对象不确定 好了 当运行到 pShape Draw 时 对象出来了 它由 pShape 指针标出 我们找到这个对 象后 就可以找到它里面的 vptr 在对象的起始处 有了 vptr 后 我们就找到了 VTable 调用的函数就在眼前了 等等 VTable 中方法那么多 我究竟使用哪个呢 不用 着急 编译器早已为我们做好了记录 编译器在创建 VTable 时 已经为每个 virtual 函数 安排好了座次 并且把这个索引号记录了下来 因此 当编译器解析到 pShape Draw 的 时候 它已经悄悄的将函数的名字用索引号来代替了 这时候 我们通过这个索引号就可以 在 VTable 中得到一个函数地址 Call it 在这里 我们就体会到为什么会有第二条规定了 通常 我们都是用基类的指针来引用 派生类的对象 但是不管具体对象是哪个派生类的 我们都可以使用相同的索引号来取得对 应的函数实现 现实中有一个例子其实跟这个蛮像的 报警电话有 110 119 120 VTable 中不同的 方法 不同地方的人拨打不同的号码所产生的结果都是不一样的 譬如 在三环外的一个 人 具体对象 跟一环内的一个人 另外一个具体对象 打 119 最后调用的消防队肯定是 不一样的 这就是多态了 这是怎么实现的呢 每个人都知道一个报警中心 VTable 里面 有三个方法 110 119 120 如果三环外的一个人需要火警抢险 一个具体对象 时 它 就拨打 119 但是他肯定不知道最后是哪一个消防队会出现的 这得有报警中心来决定 报 警中心通过这个具体对象 例子中就是具体位置了 以及他说拨打的电话号码 可以理解成 索引号 报警中心可以确定应该调度哪一个消防队进行抢险 不同的动作 这样 通过 vptr 和 VTable 的帮助 我们就实现了 C 的动态绑定 当然 这仅仅是 单继承时的情况 多重继承的处理要相对复杂一点 下面简要说一下最简单的多重继承的情 况 至于虚继承的情况 有兴趣的朋友可以看看 Lippman 的 Inside the C Object Model 这里暂时就不展开了 主要是自己还没搞清楚 况且现在多重继承都不怎么使用 了 虚继承应用的机会就更少了 首先 我要先说一下多重继承下对象的内存布局 也就是说该对象是如何存放本身的 数据的 class Cute public int i virtual void cute cout Cute cute endl class Pet public int j virtual void say cout Pet say endl class Dog public Cute public Pet public int z void cute cout Dog cute endl void say cout Dog say endl 在上面这个例子中 一个 Dog 对象在内存中的布局如下所示 Dog Vptr1 Cute i Vptr2 Pet j Dog z 也就是说 在 Dog 对象中 会存在两个 vptr 每一个跟所继承的父类相对应 如果我 们要想实现多态 就必须在对象中准确地找到相应的 vptr 以调用不同的方法 但是 如 果根据单继承时的逻辑 也就是 vptr 放在指针指向位置的起始处 那么 要在多重继承情 况下实现 我们必须保证在将一个派生类的指针隐式或者显式地转换成一个父类的指针时 得到的结果指向相应派生类数据在 Dog 对象中的起始位置 幸好 这工作编译器已经帮我们 完成了 上面的例子中 如果 Dog 向上转换成 Pet 的话 编译器会自动计算 Pet 数据在 Dog 对象中的偏移量 该偏移量加上 Dog 对象的起始位置 就是 Pet 数据的实际地址了 int main Dog d new Dog cout Dog object addr d endl Cute c d cout Cute type addr c endl Pet p d cout Pet type addr p endl delete d output Dog object addr 0 x3d24b0 Cute type addr 0 x3d24b0 Pet type addr 0 x3d24b8 正好指向 Dog 对象的 vptr2 处 也就是 Pet 的数据 好了 既然编译器帮我们自动完成了不同父类的地址转换 我们调用虚函数的过程也 就跟单继承统一起来了 通过具体对象 找到 vptr 通常指针的起始位置 因此 Cute 找到 的是 vptr1 而 Pet 找到的是 vptr2 通过 vptr 我们找到 VTable 然后根据编译时得到 的 VTable 索引号 我们取得相应的函数地址 接着就可以马上调用了 在这里 顺便也提一下两个特殊的方法在多态中的特别之处吧 第一个是构造函数 在构造函数中调用虚函数是不会有多态行为的 例子如下 class Pet public Pet sayHello void say sayHello virtual void sayHello cout Pet sayHello endl class Dog public Pet public Dog void sayHello cout Dog sayHello sayHello delete p output Pet sayHello 直接调用的是 Pet 的 sayHello Dog sayHello 多态 第二个就是析构函数 使用多态的时候 我们经常使用基类的指针来引用派生类的对 象 如果是动态创建的 对象使用完后 我们使用 delete 来释放对象 但是 如果我们不 注意的话 会有意想不到的情况发生 class Pet public Pet cout Pet destructor endl virtual Pet cout Pet virtual destructor en

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论