精华内容
下载资源
问答
  • 多态,运行时多态和编译时多态

    千次阅读 2016-04-14 21:22:01
    ...(对面向对象语言中理解...老毕在讲到多态执行问题时,结合下面的例子,给我们总结了一套口诀:“成员变量,静态方法看左边;非静态方法:编译看左边,运行看右边。”意思是:当父类变量引用子类对象时(Fu

    本文来源于:http://blog.csdn.net/foreverhuylee/article/details/21278311


     

    (对面向对象语言中理解多态、继承很有帮助~~~)

     

    老毕在讲到多态执行问题时,结合下面的例子,给我们总结了一套口诀:“成员变量,静态方法看左边;非静态方法:编译看左边,运行看右边。”意思是:当父类变量引用子类对象时(Fu f = new Zi();

    ),在这个引用变量f指向的对象中,他的成员变量和静态方法与父类是一致的,他的非静态方法,在编译时是与父类一致的,运行时却与子类一致(发生了复写)。

    例:

    class Fu {

     intnum = 5;

    static void method4() {

     System.out.println("fu method_4");

     }

    void method3() {

     System.out.println("fu method_3");

     }

    }

    class Zi extends Fu {

     intnum = 8;

    static void method4() {

     System.out.println("zi method_4");

     }

    void method3() {

     System.out.println("zi method_3");

     }

    }

    class DuoTaiDemo4 {

     public static void main(String[] args) {

    Fu f = new Zi();

     System.out.println(f.num);//与父类一致

     f.method4();//与父类一致

     f.method3();//编译时与父类一致,运行时与子类一致

      Ziz = new Zi();

     System.out.println(z.num);

     z.method4();

     z.method3();

     }

    }

    输出结果:

    5

    fu method_4

    zi method_3

    8

    zi method_4

    zi method_3

     

     

    个人分析:

    Fu f = new Zi();----------首先了解变量F到底是什么,把这句子分2段:Fu f;这是声明一个变量f为Fu这个类,那么知道了f肯定是Fu类。然后我们f=newZi();中建立一个子类对象赋值给了f,结果是什么??

    结果是,拥有了被Zi类函数覆盖后的Fu类对象----f------。

     

    -------------------------------------------也就是说:

    只有子类的函数覆盖了父类的函数这一个变化,但是f肯定是Fu这个类,也就是说f不可能变成其他比如Zi这个类等等(突然f拥有了Zi类特有函数,成员变量等都是不可能的)。所以f所代表的是函数被复写后(多态的意义)的一个Fu类,而Fu类原来有的成员变量(不是成员函数不可能被复写)没有任何变化----------------获得结论:A:成员变量:编译和运行都看Fu。

    但是f的Fu类函数被复写了。--------------获得结论:B:非静态方法:编译看Fu,运行看Zi

    对于静态方法:编译和运行都看Fu!!

    其实很简单,首先我们要理解静态情况下发生了什么?

    ----------------当静态时,Fu类的所有函数跟随Fu类加载而加载了。也就是Fu类的函数(是先于对象建立之前就存在了,无法被后出现的Zi类对象所复写的,所以没发生复写,那么获得:C:静态方法:编译和运行都看Fu。


    展开全文
  • 多态就是对于不同对象响应同一个方法时做出的不同反应,它是建立在继承的基础上面的。 1.继承于同一父类的子类,它们本身具有自己的特征 2.继承于同一父类的子类,在执行同一命令的时做出不同的响应 多态的...

    封装

    封装,就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

    封装可以起到隐藏内部实现和稳定外部接口的作用。使用封装具有以下几个好处:

    • 使用起来更加简单
    • 变量更加安全
    • 可以隐藏内部实现
    • 开发速度更加快捷

      其中:

    • 类封装了属性和方法
    • 方法封装了实现的代码
    • 属性封装了成员变量

      示例:

      //在OC中,几乎所有的类都继承于NSObject
      @interface Student : NSObject
      {
      //使用类来封装成员变量????
      //    NSString *_name;
      //    NSInteger _age;
      //    NSString *_homeAddress;
      }
      
      //使用@property封装成员变量,实现变量安全
      @property (nonatomic,strong)NSString *name;
      
      @property (nonatomic,assign)NSInteger age;
      
      @property (nonatomic,strong)NSString *homeAddress;
      
      //使用类来封装了功能代码
      -(void)helloWorld;
      
      //私有方法:@interface中没有相关的声明的方法,可以把它们看成私有方法,
      -(void)hiGuys{
      NSLog(@"我是私有方法");
      NSLog(@"%s",__func__);//打印属于哪个类的方法
      ]

      继承

      继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。 继承现有类 + 扩展

      继承是避免冗余,提高代码的可重用行和可维护性的有效手段。
      继承的传递性:直接父类 间接父类
      继承概念下的 is-a 关系是个单向的关系,子类具有父类的属性和行为,以及自身特殊的属性和行为。

      继承的特点:
      1.使用继承可以实现代码的复用,减少代码冗余
      2.OC中一个类可以继承另一个类,被继承的类称为父类或超类(基类),继承的类称为子类或派生类(孩子类)。
      3.子类可以直接“拥有”父类中所有允许子类继承的属性和方法
      4.子类可以改变父类中已有的方法,执行不同的代码实现
      5.OC中只允许单一继承,因为多重继承会有称为“致命方块”的问题

      语法:
      @interface 类名:父类名,其中冒号含义是扩展。其中self关键字在实例方法中表示该对象的指针,通过self方法可以调用自己的属性和方法。但是self在类方法中表示该类的指针。super关键字实际上是告诉子类调用父类的方法,如果父类没有定义该方法,则继续在继承链上查找,知道找到位置;如果查到NSobject中仍然未找到,则报错。子类重写父类方法时,调用父类的方法是一个比较好的习惯。

      多态

      多态就是对于不同对象响应同一个方法时做出的不同反应,它是建立在继承的基础上面的。
      1.继承于同一父类的子类,它们本身具有自己的特征
      2.继承于同一父类的子类,在执行同一命令的时做出不同的响应

      多态的好处:

      1.可以简化编程接口

    • 允许多个类中定义同一消息接口
    • 可以定义一个通用的调用方法,以简化调用

      2.把不同的子类对象都当做父类来看

    • 可以屏蔽不同子类对象之间的差异,写出通用的代码
    • 做出通用的编程,以适应需求的不断变化

      示例:

    //1.可以简化编程接口
     id animal = nil;//由于id类型的通用性质,我们可以将创建好任意对象赋值给animal
    
     animal = [Cat new];
     [animal eat];
    
     animal = [Dog new];
     [animal eat];
    
     //        2.把不同的子类对象都当做父类来看
     Animal *animalB = nil;
    
     animalB = [Cat new];
     [animalB eat];
    
     animalB = [Dog new];
     [animalB eat];
    
     /*
     开闭原则 和 里氏替换原则
    
     开闭原则:对扩展开放,对修改关闭
    
     里氏替换原则:任何基类可以出现的地方,子类一定可以出现
     */
    
    展开全文
  • 深入剖析C#的多态

    千次阅读 2007-01-08 20:34:00
    一、什么是多态 面向对象程序设计中的另外一个重要概念是多态性。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。可以把一组对象放到一个数组中,然后调用它们的方法,在这种场合下,多态性作 用就...
    一、什么是多态 
    

     

    面向对象程序设计中的另外一个重要概念是多态性。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。可以把一组对象放到一个数组中,然后调用它们的方法,在这种场合下,多态性作

    用就体现出来了,这些对象不必是相同类型的对象。当然,如果它们都继承自某个类,你可以把这些派生类,都放到一个数组中。如果这些对象都有同名方法,就可以调用每个对象的同名方法。

    同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。多态性通过派生类重载基类中的虚函数型方法来实现。

    在面向对象的系统中,多态性是一个非常重要的概念,它允许客户对一个对象进行操作,由对象来完成一系列的动作,具体实现哪个动作、如何实现由系统负责解释。

    “多态性”一词最早用于生物学,指同一种族的生物体具有相同的特性。在C#中,多态性的定义是:同一操作作用于不同的类的实例,不同的类将进行不同的解释,最后产生不同的执行结果。C#支持两种类型的多态性:

    ● 编译时的多态性

    编译时的多态性是通过重载来实现的。对于非虚的成员来说,系统在编译时,根据传递的参数、返回的类型等信息决定实现何种操作。

    ● 运行时的多态性

    运行时的多态性就是指直到系统运行时,才根据实际情况决定实现何种操作。C#中,运行时的多态性通过虚成员实现。

    编译时的多态性为我们提供了运行速度快的特点,而运行时的多态性则带来了高度灵活和抽象的特点。

    二、实现多态

    多态性是类为方法(这些方法以相同的名称调用)提供不同实现方式的能力。多态性允许对类的某个方法进行调用而无需考虑该方法所提供的特定实现。例如,可能有名为 Road 的类,它调用另一个类的 Drive 方法。这另一个类 Car 可能是 SportsCar 或 SmallCar,但二者都提供 Drive 方法。虽然 Drive 方法的实现因类的不同而异,但 Road 类仍可以调用它,并且它提供的结果可由 Road 类使用和解释。

    可以用不同的方式实现组件中的多态性:

    ● 接口多态性。

    ● 继承多态性。

    ● 通过抽象类实现的多态性。

    接口多态性

    多个类可实现相同的“接口”,而单个类可以实现一个或多个接口。接口本质上是类需要如何响应的定义。接口描述类需要实现的方法、属性和事件,以及每个成员需要接收和返回的参数类型,但将这些成员的特定实现留给实现类去完成。

    组件编程中的一项强大技术是能够在一个对象上实现多个接口。每个接口由一小部分紧密联系的方法、属性和事件组成。通过实现接口,组件可以为要求该接口的任何其他组件提供功能,而无需考虑其中所包含的特定功能。这使后续组件的版本得以包含不同的功能而不会干扰核心功能。其他开发人员最常使用的组件功能自然是组件类本身的成员。然而,包含大量成员的组件使用起来可能比较困难。可以考虑将组件的某些功能分解出来,作为私下实现的单独接口。

    根据接口来定义功能的另一个好处是,可以通过定义和实现附加接口增量地将功能添加到组件中。优点包括:

    1.简化了设计过程,因为组件开始时可以很小,具有最小功能;之后,组件继续提供最小功能,同时不断插入其他的功能,并通过实际使用那些功能来确定合适的功能。

    2.简化了兼容性的维护,因为组件的新版本可以在添加新接口的同时继续提供现有接口。客户端应用程序的后续版本可以利用这些接口的优点。

    通过继承实现的多态性

    多个类可以从单个基类“继承”。通过继承,类在基类所在的同一实现中接收基类的所有方法、属性和事件。这样,便可根据需要来实现附加成员,而且可以重写基成员以提供不同的实现。请注意,继承类也可以实现接口,这两种技术不是互斥的。

    C# 通过继承提供多态性。对于小规模开发任务而言,这是一个功能强大的机制,但对于大规模系统,通常证明会存在问题。过分强调继承驱动的多态性一般会导致资源大规模地从编码转移到设计,这对于缩短总的开发时间没有任何帮助。

    何时使用继承驱动的多态性呢?使用继承首先是为了向现有基类添加功能。若从经过完全调试的基类框架开始,则程序员的工作效率将大大提高,方法可以增量地添加到基类而不中断版本。当应用程序设计包含多个相关类,而对于某些通用函数,这些相关类必须共享同样的实现时,您也可能希望使用继承。重叠功能可以在基类中实现,应用程序中使用的类可以从该基类中派生。抽象类合并继承和实现的功能,这在需要二者之一的元素时可能很有用。

    通过抽象类实现的多态性

    抽象类同时提供继承和接口的元素。抽象类本身不能实例化,它必须被继承。该类的部分或全部成员可能未实现,该实现由继承类提供。已实现的成员仍可被重写,并且继承类仍可以实现附加接口或其他功能。

    抽象类提供继承和接口实现的功能。抽象类不能示例化,必须在继承类中实现。它可以包含已实现的方法和属性,但也可以包含未实现的过程,这些未实现过程必须在继承类中实现。这使您得以在类的某些方法中提供不变级功能,同时为其他过程保持灵活性选项打开。抽象类的另一个好处是:当要求组件的新版本时,可根据需要将附加方法添加到基类,但接口必须保持不变。

    何时使用抽象类呢?当需要一组相关组件来包含一组具有相同功能的方法,但同时要求在其他方法实现中具有灵活性时,可以使用抽象类。当预料可能出现版本问题时,抽象类也具有价值,因为基类比较灵活并易于被修改。 

     

    示例:实现多态性的程序

    using System ;

    public class DrawingBase

    {

    public virtual void Draw( )

    {

    <script language="JavaScript1.1" src="http://ad.ccw.com.cn/adshow.asp?positionID=38&js=1&innerJs=1" type="text/javascript"></script>
    Console.WriteLine("I'm just a generic drawing object.") ;

    }

    }

    public class Line : DrawingBase

    {

    public override void Draw( )

    { Console.WriteLine("I'm a Line.") ; }

    }

    public class Circle : DrawingBase

    {

    public override void Draw( )

    { Console.WriteLine("I'm a Circle.") ; }

    }

    public class Square : DrawingBase

    {

    public override void Draw( )

    { Console.WriteLine("I'm a Square.") ; }

    }

    public class DrawDemo

    {

    public static int Main(string[] args)

    {

    DrawingBase [] dObj = new DrawingBase [4];

    dObj[0] = new Line( ) ;

    dObj[1] = new Circle( ) ;

    dObj[2] = new Square( ) ;

    dObj[3] = new DrawingBase( ) ;

    foreach (DrawingBase drawObj in dObj)

    drawObj.Draw( ) ;

    return 0;

    }

    }

    说明:上面程序演示了多态性的实现。在DrawDemo类中的Main( )方法中,创建了一个数组,数组元素是DrawingBase类的对象。该数组名为dObj,是由四个DrawingBase类型的对象组成。接下来,初始化dObj数组,由于Line,Circle和Square类都是DrawingBase类的派生类,所以这些类可以作为dObj数组元素的类型。如果C#没有这种功能,你得为每个类创建一个数组。继承的性质可以让派生对象当作基类成员一样用,这样就节省了编程工作量。 一旦数组初始化之后,接着是执行foreach循环,寻找数组中的每个元素。在每次循环中,dObj 数组的每个元素(对象)调用其Draw( )方法。多态性体现在:在运行时,各自调用每个对象的Draw( )方法。尽管dObj 数组中的引用对象类型是DrawingBase,这并不影响派生类重载DrawingBase类的虚方法Draw( )。 在dObj 数组中,通过指向DrawingBase基类的指针来调用派生类中的重载的Draw( )方法。

    输出结果是:

    I'm a Line.

    I'm a Circle.

    I'm a Square.

    I'm just a generic drawing object.

    在DrawDemo 程序中,调用了每个派生类的重载的Draw( )方法。 最后一行中,执行的是DrawingBase类的虚方法Draw( )。这是因为运行到最后,数组的第四个元素是DrawingBase类的对象。

     

     

    三、虚方法

    当类中的方法声明前加上了virtual 修饰符,我们称之为虚方法,反之为非虚。使用了virtual 修饰符后,不允许再有static, abstract, 或override 修

    <script language="JavaScript1.1" src="http://ad.ccw.com.cn/adshow.asp?positionID=38&js=1&innerJs=1" type="text/javascript"></script>
    饰符。

    示例1:带有虚方法的类

    using System ;

    public class DrawingBase

    {

    public virtual void Draw( )

    { Console.WriteLine("这是一个虚方法!") ; }

    }

    说明:这里定义了DrawingBase类。这是个可以让其他对象继承的基类。该类有一个名为Draw( )的方法。Draw( )方法带有一个virtual修饰符,该修饰符表明:该基类的派生类可以重载该方法。DrawingBase类的 Draw( )方法完成如下事情:输出语句"这是一个虚方法!"到控制台。

    示例2:带有重载方法的派生类

    using System ;

    public class Line : DrawingBase

    {

    public override void Draw( )

    { Console.WriteLine("画线.") ; }

    }

    public class Circle : DrawingBase

    {

    public override void Draw( )

    { Console.WriteLine("画圆.") ; }

    }

    public class Square : DrawingBase

    {

    public override void Draw( )

    { Console.WriteLine("画正方形.") ; }

    }

    说明:上面程序定义了三个类。这三个类都派生自DrawingBase类。每个类都有一个同名Draw( )方法,这些Draw( )方法中的每一个都有一个重载修饰符。重载修饰符可让该方法在运行时重载其基类的虚方法,实现这个功能的条件是:通过基类类型的指针变量来引用该类。

    对于非虚的方法,无论被其所在类的实例调用,还是被这个类的派生类的实例调用,方法的执行方式不变。而对于虚方法,它的执行方式可以被派生类改变,这种改变是通过方法的重载来实现的。

    下面的例子说明了虚方法与非虚方法的区别。

    using System ;

    class A

    {

    public void F( ) { Console.WriteLine("A.F") ; }

    public virtual void G( ) { Console.WriteLine("A.G") ; }

    }

    class B: A

    {

    new public void F( ) { Console.WriteLine("B.F") ; }

    public override void G( ) { Console.WriteLine("B.G") ; }

    }

    class Test

    {

    static void Main( )

    {

    B b = new B( ) ;

    A a = b;

    a.F( ) ;

    b.F( ) ;

    a.G( ) ;

    b.G( ) ;

    }

    }

    例子中,A 类提供了两个方法:非虚的F 和虚方法G 。类B 则提供了一个新的非虚的方法F, 从而覆盖了继承的F; 类B 同时还重载了继承的方法G 。那么输出应该是:A.F B.F B.G B.G

    注意到本例中,方法a.G( ) 实际调用了B.G,而不是A.G,这是因为编译时值为A,但运行时值为B ,所以B 完成了对方法的实际调用。

     

     

    在派生类中对虚方法进行重载

    先让我们回顾一下普通的方法重载,普通的方法重载指的是:类中两个以上的方法(包括隐藏的继承而来的方法),取的名字相同,只要使用的参数类型或者参数个数不同,编译器便知

    <script language="JavaScript1.1" src="http://ad.ccw.com.cn/adshow.asp?positionID=38&js=1&innerJs=1" type="text/javascript"></script>
    道在何种情况下应该调用哪个方法。

    而对基类虚方法的重载是函数重载的另一种特殊形式。在派生类中重新定义此虚函数时,要求的是方法名称,返回值类型、参数表中的参数个数、类型顺序都必须与基类中的虚函数完全一致。在派生类中声明对虚方法的重载,要求在声明中加上override 关键字,而且不能有new, static 或virtual 修饰符。

    看一个用汽车类的例子来说明多态性的实现的程序:

    using System ;

    class Vehicle//定义汽车类

    {

    public int wheels; //公有成员轮子个数

    protected float weight; //保护成员重量

    public Vehicle(int w,float g)

    {

    wheels = w;

    weight = g;

    }

    public virtual void Speak( )

    {

    Console.WriteLine( " the w vehicle is speaking!" ) ;

    }

    };

    class Car:Vehicle //定义轿车类

    {

    int passengers; //私有成员乘客数

    public Car(int w,float g,int p) : base(w,g)

    {

    wheels = w;

    weight = g;

    passengers = p;

    }

    public override void Speak( )

    {

    Console.WriteLine( " The car is speaking:Di-di!" ) ;

    }

    }

    class Truck:Vehicle //定义卡车类

    {

    int passengers; //私有成员乘客数

    float load; //私有成员载重量

    public Truck (int w,float g,int p, float l) : base(w,g)

    {

    wheels = w;

    weight = g;

    passengers = p;

    load = l;

    }

    public override void Speak( )

    {

    Console.WriteLine( " The truck is speaking:Ba-ba!" ) ;

    }

    public static void Main( )

    {

    Vehicle v1 = new Vehicle(0,0 ) ;

    Car c1 = new Car(4,2,5) ;

    Truck t1 = new Truck(6,5,3,10) ;

    v1.Speak( ) ;

    v1 = c1;

    v1.Speak( ) ;

    c1.Speak( ) ;

    v1 = t1;

    v1.Speak( ) ;

    t1.Speak( ) ;

    }

    }

    分析上面的例子我们看到:

    ● Vehicle 类中的Speak 方法被声明为虚方法,那么在派生类中就可以重新定义此方法。

    ● 在派生类Car 和Truck 中分别重载了Speak 方法,派生类中的方法原型和基类中的方法原型必须完全一致。

    ● 在Test 类中,创建了Vehicle 类的实例v1, 并且先后指向Car 类的实例c1 和Truck 类的实例t1。

    运行该程序结果应该是:

    The Vehicle is speaking!

    The car is speaking:Di-di!

    The car is speaking:Di-di!

    The truck is speaking:Ba-ba!

    The truck is speaking:Ba-ba!

    这里,Vehicle 类的实例v1 先后被赋予Car 类的实例c1, 以及Truck 类的实例t1的值。在执行过程中,v1 先后指代不同的类的实例,从而调用不同的版本。这里v1 的Speak 方法实现了多态性,并且v1.Speak 究竟执行哪个版本,不是在程序编译时确定的,而是在程序的动态运行时,根据v1 某一时刻的指代类型来确定的,所以还体现了动态的多态性。

     

     

    四、接口多态性

    多个类可实现相同的“接口”,而单个类可以实现一个或多个接口。接口本质上是类需要如何响应的定义。接口描述类需要实现的方法、属性和事件,以及每个成员需要接收和返回的参数类型,但将

    <script language="JavaScript1.1" src="http://ad.ccw.com.cn/adshow.asp?positionID=38&js=1&innerJs=1" type="text/javascript"></script>
    这些成员的特定实现留给实现类去完成。

    组件编程中的一项强大技术是能够在一个对象上实现多个接口。每个接口由一小部分紧密联系的方法、属性和事件组成。通过实现接口,组件可以为要求该接口的任何其他组件提供功能,而无需考虑其中所包含的特定功能。这使后续组件的版本得以包含不同的功能而不会干扰核心功能。

    其他开发人员最常使用的组件功能自然是组件类本身的成员。然而,包含大量成员的组件使用起来可能比较困难。可以考虑将组件的某些功能分解出来,作为私下实现的单独接口。

    根据接口来定义功能的另一个好处是,可以通过定义和实现附加接口增量地将功能添加到组件中。优点包括:

    ● 简化了设计过程,因为组件开始时可以很小,具有最小功能;之后,组件继续提供最小功能,同时不断插入其他的功能,并通过实际使用那些功能来确定合适的功能。

    ● 简化了兼容性的维护,因为组件的新版本可以在添加新接口的同时继续提供现有接口。客户端应用程序的后续版本可以利用这些接口的优点(如果这样做有意义)。

    五、继承多态性

    多个类可以从单个基类“继承”。通过继承,类在基类所在的同一实现中接收基类的所有方法、属性和事件。这样,便可根据需要来实现附加成员,而且可以重写基成员以提供不同的实现。请注意,继承类也可以实现接口,这两种技术不是互斥的。

    C# 通过继承提供多态性。对于小规模开发任务而言,这是一个功能强大的机制,但对于大规模系统,通常证明会存在问题。过分强调继承驱动的多态性一般会导致资源大规模地从编码转移到设计,这对于缩短总的开发时间没有任何帮助。看下面的例子:

    class B

    { public virtual void foo () {} }

    class D : B

    {

    public override void foo () {}

    }

    //试图重载一个非虚的方法将会导致一个编译时错误,除非对该方法加上“new”关键字,//以指明该方法意欲隐藏父类的方法。

    class N : D

    {

    public new void foo () {}

    public static void Main() {

    N n = new N ();

    n.foo( ) ; // 调用N的foo

    ((D)n).foo( ) ; // 调用D的foo

    ((B)n).foo( ) ; // 调用D的foo

    }

    }

    和C++、Java相比,C#的override关键字使得阅读源代码时可以清晰地看出哪些方法是重载的。不过,使用虚方法有利有弊。第一个有利点是:避免使用虚方法轻微的提高了执行速度。第二点是可以清楚地知道哪些方法会被重载。

    我们看一个关于飞机描述的类。假设我们有一个描述飞机的基类。现在,我们要完成一个飞机控制系统,有一个全局的函数fly,它负责让传递给它的飞机起飞,那么,只需要这样:

    using System ;

    class plane

    {

    public virtual void fly(){} //起飞纯虚函数

    public virtual void land() {} //着陆纯虚函数

    public virtual string modal(){} //查寻型号纯虚函数

    }

    // 然后,我们从plane派生出两个子类,直升机(copter)和喷气式飞机(jet):

    class copter:plane

    {

    private String fModal ;

    public override void fly(){}

    public override void land(){}

    public override string modal(){}

    }

    class jet : plane

    {

    private String fModal ;

    public override void fly(){}

    public override void land(){}

    public override string modal{}

    }

    就可以让所有传给它的飞机(plane的子类对象)正常起飞!不管是直升机还是喷气机,甚至是现在还不存在的,以后会增加的飞碟。因为,每个子类都已经定义了自己的起飞方式。

    可以看到 plane.fly()函数接受参数的是 plane类对象引用,而实际传递给它的都是 plane的子类对象,多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。 很显然,parent = child; 就是多态的实质!因为直升机“是一种”飞机,喷气机也“是一种”飞机,因此,所有对飞机的操作,都可以对它们操作,此时,飞机类就作为一种接口。多态的本质就是将子类类型的指针赋值给父类类型的指针(在OP中是引用),只要这样的赋值发生了,多态也就产生了,因为实行了“向上映射”。

     

     

    展开全文
  • C++-多态性

    2017-02-25 22:46:29
    继承处理的是类与类之间的层次关系,多态则是处理类的层次结构之间以及同一个类内部同名函数的关系。简单说,多态就是在同一个类或继承体系结构的基类与派生类中,用同名函数来实现不同的功能。1、静态绑定和动态...

    多态性是面向对象程序设计语言的又一重要特性,是指不同对象接收到同一消息时会产生不同的行为。继承处理的是类与类之间的层次关系,多态则是处理类的层次结构之间以及同一个类内部同名函数的关系。简单说,多态就是在同一个类或继承体系结构的基类与派生类中,用同名函数来实现不同的功能。

    1、静态绑定和动态绑定

    多态(Polymorphism)就是指不同对象收到相同消息时会执行不同的操作。也就是用一个相同名称定义许多不同的函数,这些函数可以针对不同数据类型实现相同或相似的功能,即所谓的一个接口,多种实现。

    多态性与联编密切相关。一个源程序需要经过编译、连接才能形成可执行文件,在这个过程中要把调用函数名与对应函数关联在一起,这个过程就是绑定(binding),又称为联编。

    绑定分为静态绑定和动态绑定。

    静态绑定:即静态联编,指在编译程序时根据调用函数提供的信息,把它所对应的具体函数确定下来,即在编译时就把调用函数名与具体函数绑定在一起。

    动态绑定:即动态联编,指编译时还不能确定函数调用所对应的具体函数,只有在程序运行过程中才能够确定函数调用所对应的具体函数。

    C++中这两种都可以实现多态性,静态多态性是通过函数重载和运算符重载在编译时通过静态绑定实现的;动态多态性是通过继承和虚函数在程序执行时通过动态绑定实现的。

    平常所说的面向对象程序设计的多态性常指运行时的多态性。
    静态多态性执行速度快;动态多态性在执行时需要从多个同名函数中匹配调用函数,所以比静态多态性的执行效率低,但提供了更多的灵活性、问题的抽象性和程序的可维护性。

    2、虚函数

    2.1、虚函数的意义

    虚函数是运行时多态的基础。
    虚函数只能在类中定义。

    虚函数的运行机制:
    如果基类中的非静态成员函数被定义为虚函数,且当派生类重写了(在派生类中定义的成员函数,如果它的函数原型与其基类中的某个成员函数完全相同,就称为重写)基类的虚函数,当通过指向基类对象的指针或引用调用派生类对象中的虚函数时,就会调用到该指针或引用实际所指对象的成员函数。否则调用的就是该基类中的函数。

    virtual的意义在于只是编译器,对这类函数采用延后联编(动态绑定)的方法,在程序运行过程中才确定与之相对应的函数。反之则早期联编。

    2.2、虚函数的特性

    1、一旦将某个成员函数声明为虚函数,他在类的继承体系中就永远为虚函数了,几时派生类在重写该函数时并没有将它声明为虚函数。
    如果定义虚函数的类从其它类派生,这些虚函数不会影响基类中的同名成员函数,保持原有特性。

    2、如果基类定义了虚函数,当通过基类指针或引用调用派生类对象时,将访问到他们实际所指对象中的虚函数版本。

    3、只有通过基类对象的指针或引用访问派生类对象的虚函数时,才能体现虚函数的特性。当通过普通的基类对象访问派生类对象时,不能实现虚函数的特性,只能访问到派生类中的基类子对象中的成员。

    4、派生类中的虚函数要保持其虚特征,必须与基类虚函数的函数原型完全相同,否则就是普通的重载函数,与基类的虚函数无关。

    5、派生类通过从基类继承的成员函数调用虚函数时,将访问到派生类中的版本。根据特征3可以解释。

    6、只有类的非静态成员函数才能被定义为虚函数,类的构造函数和静态成员函数不能定义为虚函数。因为虚函数在继承层次结构中才能够发生作用,而构造函数、静态成员不能够被继承。

    7、内联函数也不能是虚函数。因为内联函数采用的是静态联编的方式,而虚函数是在程序运行时才与具体函数动态绑定的,采用的是动态联编的方式。即使虚函数在类体内被定义,C++编译器也将他视为非内联函数。

    3、虚析构函数

    虚函数可以被派生类继承,并且要求派生类与基类中的虚函数具有相同的函数名和参数列表,但构造函数与虚构函数与这两个特性违背。首先二者不能被派生类继承,其次名字都是与类同名。

    尽管如此,C++却允许将析构函数定义为虚函数,如果基类的析构函数是虚函数,则所有直接或间接派生类的析构函数都是虚函数。

    在销毁通过基类指针(或引用)调用的派生类对象时,虚析构函数可以确保能够彻底地回收对象占用的内存空间。在销毁自由存储空间中用new建立的对象时,虚析构函数可以确保在用delete销毁动态分配的派生类对象时调用到正确的析构函数,完成对象所占用内存空间的回收。

    比如当基类对象的指针(或引用)调用派生类对象时,如果基类析构函数不是虚函数,则通过基类指针(或引用)对派生类的析构很可能是不彻底的。

    如果析构函数是虚函数,在通过基类对象的指针(或引用)销毁派生类对象时,同时调用了基类和派生类的析构函数。

    4、纯虚函数与抽象类

    有些情况下,在定义类的时候并不知道如何实现它的某些成员函数,定义该类的目的也并不是为了建立它的对象,而是为了把它作为派生其他类的基类,并通过此类访问派生类对象。有些在基类中无法实现的成员函数,在派生类中却有具体的是实现方法。

    4.1纯虚函数和抽象类

    纯虚函数在声明时被初始化为0的类成员函数,如:
    class X {

    virtual retutn_type func_name () = 0;
    }
    纯虚函数在基类中声明,但要求在基类中没有具体的函数实现代码,要求继承它的派生类为纯虚函数提供实现代码。

    在一个类中可以声明一个或多个纯虚函数,只要有纯虚函数的类就是抽象类。抽象类只能作为其它类的基类,不能用来建立对象,所以又称为抽象基类。

    C++对抽象类有限定:
    1、抽象类中含有纯虚函数,由于纯虚函数没有实现代码,所以不能建立抽象类的对象。

    2、抽象类只能作为其它类的基类,可以通过抽象类对象的指针或引用访问到它的派生类对象,实现运行时的多态性。

    3、如果派生类只是简单的继承了抽象类的纯虚函数,而没有重新定义基类的纯虚函数,则派生类也是一个抽象类。

    4.2、抽象类的应用

    在设计类的继承结构时,可以把各派生类都需要的功能设计成抽象基类的虚函数,每个派生类根据自己的情况重新定义虚函数的功能,以便描述每个类特有的行为。由于抽象基类具有各派生类成员函数的虚函数版本,可以把它作为访问整个继承结构的接口,通过基类的指针或引用访问在各个派生类中实现的虚函数,这种方式也称为接口重用,即不同的派生类都可以把抽象基类作为接口,让其他程序通过此接口访问各派生类的功能。

    在设计类继承层次结构时,常把各类都具有的通用功能抽象成虚函数或纯虚函数,放在最上层的抽象基类中,派生类再具体实现这些虚函数,完成本类需要的功能。再以抽象基类为接口,就能够访问到整个继承结构中的每个类定义的虚函数,不在需要针对各个具体类编写独立的应用程序,简化了软件设计的复杂度,也给软件的功能扩展和维护带来了极大的方便。

    5、运行时类型信息

    Run-Time Type Information,RTTI,提供了在程序运行时刻确定对象类型的方法,是面向对象程序语言位解决多态引入的一种语言特性。

    早期非面向对象是一种静态数据语言,而多态要求C++指针或引用类型可能与它们实际代表的类型不一样(如及类指针可以指向派生类对象),当将一个多态指针转换为其实际指向对象的类型时,就需要知道对象的类型信息,而这些信息之中能在程序运行时确定。

    5.1、dynamic_cast

    强制类型转换操作符,用于多态基类指针(引用)与派生类指针(引用)之间的转换,在运行时执行,而const_cast、static_cast和reinterpret_cast强制类型转换则是在编译时完成的。

    dynamic_cast<目标类型>(表达式)

    指针转换失败,返回0;引用转换失败,引发异常。

    5.2、typeid

    头文件为typeinfo
    用法为typeid(exp)
    exp可以使类对象、指针或引用,返回类对象的一个引用。

    若基类没有虚函数,则返回的是基类类型;反之返回对应的派生类类型。实际编程中,常利用这个对变量或对象的实际类型进行识别,并针对识别出的类型做一些特殊处理。

    Visual C++环境下,要使用typeid,必须设置RTTI机制。

    展开全文
  • java基础

    2021-07-02 09:39:31
    //15 现在几时 System.out.println(d.getMinutes());//15 现在几分 System.out.println(d.getMonth());//6 (输出的数字加1就是现在的月份)现在几月 System.out.println(d.getSeconds());//43 现在几秒 System.out....
  • 多态 Polymorphism Problem: How to handle alternative based on types? 如何处理基于变化的类型? How to create pluggable software components? 如何创建可插拔的组建? Alternatives based on type ...
  • Java的静态方法不能被重写(详解!)

    万次阅读 多人点赞 2017-06-07 00:18:15
    答案很明确:java的静态...重写的目的在于根据创造对象的所属类型不同而表现出多态。因为静态方法无需创建对象即可使用。没有对象,重写所需要的“对象所属类型” 这一要素不存在,因此无法被重写。千言万语,不如代
  • 第十一章 使用类(1)

    2016-11-20 16:32:42
    运算符重载是一种形式的多态,它允许赋予C++运算符多种含义。比如我们想做这样的加法evening = sam + janet ,这三个都是三个对象,这时候我们就要对加号重载。 运算符函数的格式如下 operatorop(argument-
  • java笔记

    2014-11-27 12:05:00
    计算机高级编程语言:解释型语言:asp php 解释一句执行一句编译型语言:c c++ java 是两者的结合体 (编写 编译 运行),属于强...java的三大特性 平台无关性 安全性 网络移动性面向对象特性封装 继承 多态 抽象...
  • C++发展简史

    千次阅读 2017-12-13 16:56:22
    C++是C语言的继承,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行以继承和多态为特点的面向对象的程序设计。C++擅长面向对象程序设计的同时,还可以进行...
  • 面向对象的3个基本要素:封装、继承、多态 面向对象的5个基本设计原则: 单一职责原则(Single-Resposibility Principle)  其核心思想为:一个类,最好只做一件事,只有一个引起它的变化。单一职责原则...
  • 主要特征:封装、继承、多态。 2.SESSION 与 COOKIE的区别是什么,请从协议,产生的原因与作用说明? (1)、http无状态协议,不能区分用户是否是从同一个网站上来的,同一个用户请求不同的页面不能看做是同一个用户...
  • 再到面向对象的重点、难点:类、对象、封装、接口、继承、多态、虚方法等。之后学习C#.Net中的Winform平台,其间夹杂着Socket网络编程、简单的HTML+CSS,到最后的XML。由此,对C#.Net的基础学习终于学完了,终于学...
  • 1.java基础详解

    2021-08-18 20:28:40
    final, private ,构造方法不能被重写 多态的特性增强程序的通用性 / 统一标准 多态:多态根本不关心具体的子类类型,屏蔽掉了子类之间的不同,把子类当父类来看 2.异常 捕捉异常,可能出错的语句块写在try语句快中,...
  • windows类书的学习心得

    万次阅读 2014-07-30 13:11:46
    本书分为四大篇。第一篇讲SDK编程,简单明了的概括了SDK程序设计的主要原理,提供进入MFC核心技术以及应用技术之前的所有技术基础,特别是Windows的消息机制,讲得很清楚。第二篇介绍Visual C++整合环境开发工具,...
  • ♢it段子娱乐

    2013-10-25 21:17:22
    面试官:“知道多态么?”。应聘者:“知道,我很保守的。我认为让心爱的女人为了自已一时的快乐去堕胎是不道德的行为!请问这和C#有什么关系?”。面试官:“好吧,你被录取了!” 8、IT工程师=加班狂+程序员+测试...
  • VIVO校招C++岗面经(笔试+一面+二面+Offer)

    万次阅读 多人点赞 2018-12-08 17:23:43
     A:简单说了一下虚函数表在C++类继承中的语法规则,函数重写,以及如何体现多态等。   Q:说一下static的用法?  A:简单的说了一下static在局部变量、全局变量、类成员变量的不同作用。   Q:如何防止重复...
  • 一些IT段子,娱乐一下

    千次阅读 2014-01-19 17:25:19
    面试官:“知道多态么?”。应聘者:“知道,我很保守的。我认为让心爱的女人为了自已一时的快乐去堕胎是不道德的行为!请问这和C#有什么关系?”。面试官:“好吧,你被录取了!” 8、IT工程师=加班狂+...
  • 最后不能几时重载函数,又是默认值函数。。。。。。。。。。。。。 ---------------------------------------------------------------------------------------------------------------------------------------...
  • 后边可以接跟时间长短的判断,分成刚刚,几分钟前,几小时前,几天前,几月几号几时几分几秒 得到一个从现在开始多少秒后的一个时间 NSDate * date = [NSDate dateWithTimeIntervalSinceNow:120]; NSLog(@”%@”, ...
  • JavaEE_1

    2020-12-27 18:55:35
    ) 变量分为局部变量和成员变量,成员变量又分为实例变量和静态变量(类级别的) 1.1、数据类型的作用 数据类型用来声明变量,程序在运行过程中根据不同的数据类型分配不同大小的空间。 1.2、JAVA中的数据类型有 ...
  • java Calendar类解析

    2018-09-01 09:28:32
    上面那段代码就表示了这里是实例化是一个子类对象赋给父类的过程(即多态的一种体现) buddhist是佛教的应该就是佛教的日历,japane应该是代表日本的历法(不知道为什么要给日本的历法单独写一个子类 我中华大...
  • C++面试题汇总

    2021-07-15 14:43:52
    1.1 封装 C++类将类中成员分为三种属性:private、protected、public (1)私有成员(变量和函数)只限于类成员访问,由private限定; (2)公有成员(变量和函数)允许类成员和类外的任何访问,由public限定; (3...
  • 大话设计模式

    万次阅读 多人点赞 2018-12-01 18:33:20
     对继承和多态玩的好的人几乎都会在继承体系中多多少少用到它,不如.net或者java类库的设计中,通常都会利用模板方法模式提取类库中的公共行为到抽象类中。 第十一章 无熟人难办事? -迪米特法则  迪米特法则...
  • 1-11

    万次阅读 2017-03-21 23:37:54
    =============================================================================================== for(:){ String str="asdga"; ...for(char s:str){ //char 指的是str的元素的类型 ...int
  • 诗词对联工具

    万次阅读 2005-04-20 15:14:00
    作者:海底捞针create:2004-11update:2005-4-17版权所有,随便用哦:b点击这里,可以直接打开该页面-->诗韵table{border-left:2px solid black;border-top:2px solid black;border-right:1px solid black;...
  • 我们知道,子类没有重载父类的虚函数是一件毫无意义的事情,因为多态也是要基于函数重载的。虽然在“无覆盖的一般继承”的图中我们可以看到 Base1的虚表中有Derive的虚函数 ,但我们根本不可能使用下面的语句来调用...
  • C++虚函数表详细解释及实例分析

    千次阅读 2016-04-06 22:20:01
    C++的虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖(override)的问题,保证其能真实的反应实际的函数。...
  • javaSE

    2021-04-24 21:48:46
    getHours() --现在几时; getMinutes() --现在几分; getMonth()+1 --现在几月; getSeconds() --现在几秒; getTime() --自1970.1.1零点 getYear()+1900 --自1900年算; toLocaleString() --当前完整的时间; 17....
  • 问题抛出 如题:假定期望设计一个类A,...重写的目的在于根据创造对象的所属类型不同而表现出多态。因为静态方法无需创建对象即可使用。没有对象,重写所需要的“对象所属类型” 这一要素不存在,因此无法被重写。

空空如也

空空如也

1 2 3
收藏数 52
精华内容 20
关键字:

多态分为几时多态