关于c++多态

  推荐《Inside The c++ Object Model 》, 文章转自其中一段。

  多态是面向对象的一个重要特征,c++中多态是通过虚函数机制实现的,关于c++多态实现的一些基本知识,本文就不在细述。

  通常类似这样:

     Shape * ps = new circle;

     ps->Rotate();  //调用的是虚函数

虽然ps是shape类型指针, 但是调用的是circle中的Rotate方法。这是毫无疑问的, 这样做会很易于我们程序的封装。多态的主要用途是经由一个共同的接口来影响类型的封装, 这个接口通常被定义在一个抽象的base class中。一个指针, 不管指向哪一种类型数据在我们的机器上他本身所需要的内存大小是固定的,如16位机器上是2byte,32位机器上是4byte,我们使用指针可以调用对象函数、成员, 是因为我们知道这个类型对象在内存中所占的区域大小, 通过指针自然能找到其中的成员地址以及虚函数列表指针。举个列子,下面有个ZooAnimal声明:

  class ZooAnimal {

  public:

    ZooAnimal();

    virtual ~ZooAnimal();

    //...

    virtual void rotate();

  protected:

    int loc;

    String   name;

  };

  ZooAnimal  za("Zoey");

  ZooAnimal  *pza = & za;

其中class object za 和指针pza的可能布局如图下所示:

  

但是, 一个指向ZooAnimal的指针是如何的与一个指向整数的指针或template Array(如下, 与一个String一并产生)指针有所不同呢?

  ZooAnimal * px;

  int *pi;

  Array<String> * pta;

以内存需求的观点来说, 没有什么不同!他们三个都需要足够的内存来存放一个机器地址(32位为4个bytes)。“指向不同类型之个指针”间的差异,既不在其指针表示法不同,也不再其内容(代表一个地址)不同,而是在其所寻址出来的Object类型不同。也就是说,“指针类型”会告诉编译器如何解释某个特定地址中的内存内容及其大小:

  嗯, 那么, 一个指向地址1000而类型为void*的指针,将涵盖怎样的地址空间?是的, 我们不知道!这就是为什么一个类型为void的指针只能够含有一个地址,而不能通过它操作所指的object的缘故。

  所以,转型(cast)其实是一种编译器指令,大部分情况先他并不改变一个指针所含的真正地址, 它只影响“被指出之内存的大小和其内容”的解释方式。

    现在,让我们定义一个Bear, 作为一种ZooAnimal。当然,经由“public继承”可以完成这件任务:

class Bear:public ZooAnimal{

      public :

        Bear();

        ~Bear();

        //..

        void rotate();

        virtual void dance();

      protected:

        enum Dances{...};

        Dances dances_known;

        int cell_block;

    }

    Bear  b("Yogi");

    Bear *pb = &b;

    Bear &rb = *pb;

b、pb、rb会有怎样的内存需求呢?不管是pointer或者reference都只需要4个bytes(16位上2-bytes)空间。Bear Object需要24bytes, 也就是ZooAnimal的16bytes加上Bear所带来的8bytes,,如图下展示了可能的内存布局:

    

  好, 假设,我们的Bear Object放在地址的1000处, 一个Bear指针和一个ZooAnimal指针有什么不同?

      Bear b;

      ZooAnimal *pz = &b;

      Bear * pb = &b;

  它们每个都指向Bear Object的第一个byte。 其间的差别, pb所涵盖的地址包含整个Bear object,而pz所涵盖的地址只包含Bear Object中ZooAnimal subObject 。

  除了ZooAnimal subObject中出现的members, 你不能狗使用pz来直接处理Bear的任何members。唯一的列外是通过virtual机制:

    //不合法:cell_block不是ZooAnimal的一个member

    //虽然我们知道pz当前指向衣蛾Bear Object。

    pz->cell_block;    

    //ok: 经过一个明白的downcast操作就没问题

    ((Bear*)pz)->cell_block;

    //下面这样更好, 但它是一个run-time operation(成本较高)

    if (Bear* pb2 = dynamic_cast<Bear*>(pz))

      pb2->cell_block;

    //Ok, 因为cell_block是Bear的一个member

    pb->cell_block;

  当我们写:

    pz->rotate();

时,pz的类型将在编译时期决定以下的两点

    1) 固定的可用接口。也就是说,pz只能够调用ZooAnimal的public接口

    2) 该接口的access level(列如rotate()是ZooAnimal的一个public member)

  在每一个执行点, pz所指的object类型可以决定rotate()所调用的实体。类型信息的封装并不是维护于pz之中,而是维护与link之中,此link存在于“Object的vptr” 和“vitual table”之间。

  现在, 请看看这种情况:

      Bear b;

      ZooAnima za = b; //译注:这里会引起切割(sliced)

      //调用ZooAnimal::rotate()

      za.rotate(); 

  为什么rotate()所调用的是ZooAnimal实体而不是Bear实体? 此外,如果初始化函数(译注:应用与行数assignment操作发生时)将一个object内容完整拷贝到另一个object中去, 为什么za的vtr不指向Bear的virtual table?

  第二个问题的答案是,编译器在(1)初始化(2)指定(assignment)操作(将一个class object指定给另一个class object)之间做出了仲裁。编译器必须确保如果某个Object含有一个或一个以上的vptrs,那写vptrs的内容不会被base class object初始化或改变。

  至于第一个问题的答案是:za并不是(而且也绝不会是)一个Bear,它是(并且只能是)一个ZooAnimal。多态所造成的“一个以上的类型”的潜在力量,并不能实际发挥在“直接存取objects”这件事上。有一个似是而非的观念:OO程序设计并不支持对Object的直接处理。举个例子,下面这一组定义:

    {    

      //注:Panda继承Bear ,Bear继承ZooAnimal

      ZooAnimal za;

      ZooAnimal *pza;

      

      Bear b;

      Panda *pp =new Panda;

      pza = &b;

    }

  其可能的内存布局如下图:

 

  将za或b的地址,或pp所含的内容(也是个地址)指定给pza, 显然不是问题。一个Pointer或一个reference之所以支持多台,是因为他们并不引发内存中任何“与类型有关的内存委托操作(type-dependent commitment)”;会受到改变的只是它们所指向的内存的“大小和内容解释方式”而已。

  然而,任何人如果改变Object za的大小(或是被指定为)一个derived class Object时, derived object就会被切割, 以塞入较小的base type内存中, derived type将没有留下任何蛛丝马迹。多态于是不再呈现,而一个严格的编译器可以再编译时期解析一个“通过该Object而触发的virtual function调用操作”,因而回避virtual机制。如果virtual function 被定义俄日inline,则更有效率上的大收获。

   总而言之,多态是一种威力强大的设计机制,允许你继承一个抽象的public接口之后,封装相关的类型。然而需要付出的代价就是额外的间接性--不论是在“内存的获得”或是“类型的决断”上。c++通过class 的pointer和references 来支持多态,这种程序设计风格就是“面向对象”。

  

时间: 2024-11-04 20:15:39

关于c++多态的相关文章

C#多态

通过继承,一个类可以用作多种类型:可以用作它自己的类型.任何基类型,或者在实现接口时用作任何接口类型.这称为多态性.C# 中的每种类型都是多态的.类型可用作它们自己的类型或用作 Object 实例,因为任何类型都自动将 Object 当作基类型. 多态性不仅对派生类很重要,对基类也很重要.任何情况下,使用基类实际上都可能是在使用已强制转换为基类类型的派生类对象.基类的设计者可以预测到其基类中可能会在派生类中发生更改的方面.例如,表示汽车的基类可能包含这样的行为:当考虑的汽车为小型货车或敞篷汽车时

Java基础(八):多态

一.多态的理解: 多态是同一个行为具有多个不同表现形式或形态的能力. 多态就是同一个接口,使用不同的实例而执行不同操作,如图所示: 多态性是对象多种表现形式的体现:现实中,比如我们按下 F1 键这个动作:如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档:如果当前在 Word 下弹出的就是 Word 帮助:在 Windows 下弹出的就是 Windows 帮助和支持:同一个事件发生在不同的对象上会产生不同的结果. 二.多态的优点和必要条件: 多态的优点:1. 消除类型之间的耦合关系2

当this指针成为指向之类的基类指针时,也能形成多态

this指针: 1)对象中没有函数,只有成员变量 2)对象调用函数,通过this指针告诉函数是哪个对象自己谁. 1 #include<iostream> 2 using namespace std; 3 class Shape 4 { 5 public: 6 //void cal_display(Shape* this) 7 void cal_display(){ 8 display(); 9 this->display(); 10 } 11 private: 12 virtual vo

Java多态

多态不是方法的重载,不是方法名一样方法的参数不一样,不是一个参数有多种态度就称之为多态,那是不正确的,如果这就是多态的话那么何必有方法的重载?直接改名多态就行了.父类 a = 子类对象 就是子类对象可以披上父类的衣服,只要穿上了父类的衣服就装扮成了父类 可以做父类的一些事情灵活性强.多态最重要的目的就是为了让子类转换成父类. 面向对象编程之上还有一种叫做面向功能编程,面向功能编程还可以转换成面向父类编程.比如:现实生活中,有小宝.大宝 大宝是小宝的父亲.有一天大宝不在家,小宝接到打给大宝的电话

C++中多态的实现原理

1. 用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数. 2. 存在虚函数的类都有一个一维的虚函数表叫做虚表.类的对象有一个指向虚表开始的虚指针.虚表是和类对应的,虚表指针是和对象对应的. 3. 多态性是一个接口多种实现,是面向对象的核心.分为类的多态性和函数的多态性. 4. 多态用虚函数来实现,结合动态绑定. 5. 纯虚函数是虚函数再加上= 0. 6. 抽象类是指包括至少一个纯虚函数的类. 纯虚函数:virtual void breathe()= 0:即抽象类!必须在子类实

OC多态

多态:不同对象以自己的方式响应相同的消息的能力叫做多态. 由于每个类都属于该类的名字空间,这使得多态称为可能.类定义中的名字和类定义外的名字并不会冲突.类的实例变量和类方法有如下特点:和C语言中结构体中的数据成员一样,类的实例变量也位于该类独有的名字空间.类方法也同样位于该类独有的名字空间.与C语言中的方法名不同,类的方法名并不是一个全局符号.一个类中的方法名不会和其他类中同样的方法名冲突.两个完全不同的类可以实现同一个方法.方法名是对象接口的一部分.对象收到的消息的名字就是调用的方法的名字.因

多态的内存分析-转载

java运行时,在内存里分四个部分.栈,堆,数据区和代码区..举个例子String str=new String("AAA");str就放在栈里,字符串"AAA"放在堆里.所有的方法代码都放在了代码区. public class A{public void show(){System.out.println("A");}} public class B extends A{public void show(){System.out.println

多态and接口

一.多态 1.什么是多态? 解析:不同的对象对于同一个操作,做出的响应不同 具有表现多种形态的能力的特征 2.使用多态的优点 解析:为了实现统一调用 一个小例子:<父类类型作为参数> 父类(Pet) 子类(Gog,Penguin) 主人类(Master)测试类(Test) Pet public abstract class Pet { public abstract void eat(); } Dog public class Dog extends Pet{ @Override public

初始继承和多态

一.子类与父类 1.子类:父类 例如: Dog(子类):Anomal(父类) 子类(派生类)父类(基类和超类) 2.子类可以继承父类那些成员 (非私有成员,但是从技术角度,可以认为是父类的所有成员) 软件系统中的两个类符合is a时可以使用继承 例如: student is a person se is a employee 鸵鸟(ostrish)is a bird(错误结论!!!) ☆:继承模式下子类构造背后到底发生了什么? 如果我们想构建一个子类对象 //在Animal父类中 class A

2、C#面向对象:封装、继承、多态、String、集合、文件(上)

面向对象封装 一.面向对象概念 面向过程:面向的是完成一件事情的过程,强调的是完成这件事情的动作. 面向对象:找个对象帮你完成这件事情. 二.面向对象封装 把方法进行封装,隐藏实现细节,外部直接调用. 打包,便于管理,为了解决大型项目的维护与管理. 三.什么是类? 将相同的属性和相同方法的对象进行封装,抽象出 “类”,用来确定对象具有的属性和方法. 类.对象关系:人是类,张三是人类的对象. 类是抽象的,对象是具体的.对象可以叫做类的实例,类是不站内存的,对象才占内存. 字段是类的状态,方法是类执