你的位置:首页 > ASP.net教程

[ASP.net教程][.net 面向对象编程基础] 面向对象三大特性——多态


[.net 面向对象编程基础] (13) 面向对象三大特性——多态

      前面两节,我们了解了面向对象的的封装和继承特性,面向对象还有一大特性就是多态。比起前面的封装和继承,多态这个概念不是那么好理解。我们还是从一个事例开始:

      公司最近为了陶冶情操,养了几种动物(Animal),有猫(Cat)、狗(Dog)、羊(Sheep),这些动物都有共同的特性,会吃(Eat)、会叫(Shout),但是它们吃的不同,叫的也不同。既然这样,我们能不能设计一个动物类(Animal)和它的成员(Eat方法、Shout方法)来表示这些动物的共同特征,而当我们关注猫时,猫来实现这两个成员(吃鱼、喵喵叫);当我们关注狗时,狗来实现这两个成员(吃肉和汪汪叫)。

1.什么是多态?

上述例子就是一个典型的多态,就是父类的一些成员,子类继承后去重写从而实现不同的功能。

多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。这就是多态,这种特性称为多态性。

2.多态的分类

多态性分为两种,一种是编译时的多态性,一种是运行时的多态性。

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

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

3.多态的实现

  我们知道多态有两种,一种是编译时通过重载实现,另一种是运行时,通过重写或叫覆写来实现,那么如何实现他们?

3.1编译时多态:重载(overload)

重载(overload):重载指的是同一个类中有两个或多个名字相同但是参数不同的方法,(注:返回值不能区别函数是否重载),重载没有关键字。

注意:

A.从重载的定义来看,重载是一种编译时多态

B.重载不需要事先定义可重载的方法,即没有关键字

C.重载只是针对一个类内部的几个参数不同,名称相同的方法。

我们还在本节开篇那几只陶冶情操的动物来示例说明,代码如下:

 1 /// <summary> 2 /// 狗(多态:重载事例) 3 /// </summary> 4 class Dog 5 { 6   /// <summary> 7   /// 叫 8   /// </summary> 9   public void Shout()10   {11     Console.WriteLine("汪!");12   }13 14   /// <summary>15   /// 叫(重载方法)16   /// </summary>17   public void ShoutCount(int count)18   {19     int i = 0;20     string shout = "";21     do22     {23       shout += "汪!";24       i++;25     } while (i <= count);26     Console.WriteLine(shout);27   }28 }

//调用Dog dog = new Dog();dog.Shout();dog.ShoutCount(5);

3.2运行时多态:重写

重写有两种,一种是override修饰符,另一种使用new修饰符,下面会举例说明两种重写的使用方法和异同。

重写(override):也称过载,重写是指子类对父类中虚函数或抽象函数的“覆盖”(这也就是有些书将过载翻译为覆盖的原因),但是这种“覆盖”和用new关键字来覆盖是有区别的。

下面以本节开题前例子,实现重写,代码如下:

 

 1   /// <summary> 2   /// 动物类(父类) 3   /// </summary> 4   class Animal 5   { 6    /// <summary> 7    /// 名字 8    /// 说明:类和子类可访问 9    /// </summary> 10    protected string name; 11  12  13     /// <summary> 14     /// 构造函数 15     /// </summary> 16     /// <param name="name"></param> 17     public Animal(string name) 18     { 19       this.name=name; 20     } 21  22     /// <summary> 23     /// 名字(虚属性) 24     /// </summary> 25     public virtual string MyName 26     { 27       get { return this.name; }       28  29     } 30  31     /// <summary> 32     /// 吃(虚方法) 33     /// </summary> 34     public virtual void Eat() 35     { 36       Console.WriteLine("我会吃!"); 37     } 38  39     /// <summary> 40     /// 叫(虚方法) 41     /// </summary> 42     public virtual void Shout() 43     { 44       Console.WriteLine("我会叫!"); 45     } 46   } 47  48   /// <summary> 49   /// 狗(子类) 50   /// </summary> 51   class Dog:Animal 52   { 53     string myName; 54     public Dog(string name): base(name) 55     { 56       myName = name; 57     } 58      59     /// <summary> 60     /// 名字(重写父类属性) 61     /// </summary> 62     public override string MyName 63     { 64       get { return "我是:狗狗,我叫:"+this.name; } 65  66     } 67  68  69     /// <summary> 70     /// 吃(重写父类虚方法) 71     /// </summary> 72     public override void Eat() 73     { 74       Console.WriteLine("我喜欢吃肉!"); 75     } 76  77     /// <summary> 78     /// 叫(重写父类方法) 79     /// </summary> 80     public override void Shout() 81     { 82       Console.WriteLine("汪!汪!汪!"); 83     } 84   } 85   /// <summary> 86   /// 猫(子类) 87   /// </summary> 88   class Cat : Animal 89   { 90     string myName; 91     public Cat(string name) 92       : base(name) 93     { 94       myName = name; 95     } 96     /// <summary> 97     /// 名字(重写父类属性) 98     /// </summary> 99     public override string MyName100     {101       get { return "我是:猫咪,我叫:" + this.name; }102 103     }104 105     /// <summary>106     /// 吃(重写父类虚方法)107     /// </summary>108     public override void Eat()109     {110       Console.WriteLine("我喜欢吃鱼!");111     }112 113     /// <summary>114     /// 叫(重写父类方法)115     /// </summary>116     public override void Shout()117     {118       Console.WriteLine("喵!喵!喵!");119     }120   }121 122   /// <summary>123   /// 羊(子类)124   /// </summary>125   class Sheep : Animal126   {127     string myName;128     public Sheep(string name)129       : base(name)130     {131       myName = name;132     }133     /// <summary>134     /// 名字(重写父类属性)135     /// </summary>136     public override string MyName137     {138       get { return "我是:羊羊,我叫:" + this.name; }139 140     }141 142     /// <summary>143     /// 吃(重写父类虚方法)144     /// </summary>145     public override void Eat()146     {147       Console.WriteLine("我喜欢吃草!");148     }149 150     /// <summary>151     /// 叫(重写父类方法)152     /// </summary>153     public override void Shout()154     {155       Console.WriteLine("咩!咩!咩!");156     }157   }

 

//调用方法Animal dog = new Dog("旺财");string myName=dog.MyName;Console.WriteLine(myName);dog.Eat();dog.Shout();

//运行结果如下:我是:狗狗,我叫:旺财我喜欢吃肉!汪!汪!汪!

//调用方法Animal sheep = new Sheep("美羊羊");string myName = sheep.MyName;Console.WriteLine(myName);sheep.Eat();sheep.Shout();

//运行结果如下:我是:羊羊,我叫:美羊羊我喜欢吃草!咩!咩!咩!

重写(new)

new:覆盖指的是不同类中(基类或派生类)有两个或多个返回类型、方法名、参数都相同,但是方法体不同的方法。但是这种覆盖是一种表面上的覆盖,所以也叫隐藏,被覆盖的父类方法是可以调用得到的。

下面用实例说明,代码如下:

 

 1   /// <summary> 2   /// 动物类(父类) 3   /// </summary> 4   class Animal 5   { 6    /// <summary> 7    /// 名字 8    /// 说明:类和子类可访问 9    /// </summary>10    protected string name;11 12 13     /// <summary>14     /// 构造函数15     /// </summary>16     /// <param name="name"></param>17     public Animal(string name)18     {19       this.name=name;20     }21 22     /// <summary>23     /// 名字(虚属性)24     /// </summary>25     public virtual string MyName26     {27       get { return this.name; }      28 29     }30 31     /// <summary>32     /// 吃(虚方法)33     /// </summary>34     public virtual void Eat()35     {36       Console.WriteLine("我会吃!");37     }38 39     /// <summary>40     /// 叫(虚方法)41     /// </summary>42     public virtual void Shout()43     {44       Console.WriteLine("我会叫!");45     }46   }47 48   /// <summary>49   /// 狗(子类)50   /// </summary>51   class Dog:Animal52   {53     string myName;54     public Dog(string name): base(name)55     {56       myName = name;57     }    58     /// <summary>59     /// 名字(重写父类属性)60     /// </summary>61     public override string MyName62     {63       get { return "我是:狗狗,我叫:"+this.name; }64     }65 66     /// <summary>67     /// 吃(重写父类虚方法)68     /// </summary>69     new public  void Eat()70     {71       Console.WriteLine("我喜欢吃肉!");72     }73 74     /// <summary>75     /// 叫(重写父类方法)76     /// </summary>77     public new void Shout()78     {79       Console.WriteLine("汪!汪!汪!");80     }81   }

 

//调用方法Animal dog = new Dog("旺财");string myName=dog.MyName;Console.WriteLine(myName);dog.Eat();dog.Shout();

//执行结果如下:我是:狗狗,我叫:旺财我会吃!我会叫!

如下改一下调用方法:

//调用方法Dog dog = new Dog("旺财");string myName=dog.MyName;Console.WriteLine(myName);dog.Eat();dog.Shout();

//执行结果如下:我是:狗狗,我叫:旺财!我爱吃肉!汪!汪!汪!

 

可以看出,当派生类Dog的Eat()方法使用new修饰时,Dog的对象转换为Animal对象后,调用的是Animal类中的Eat()方法。其实可以理解为,使用new关键字后,使得Dog中的Eat()方法和Animal中的Eat()方法成为毫不相关的两个方法,只是它们的名字碰巧相同而已。所以, Animal类中的Eat()方法不管用还是不用virtual修饰,也不管访问权限如何,或者是没有,都不会对Dog的Eat()方法产生什么影响(只是因为使用了new关键字,如果Dog类没用从Animal类继承Eat()方法,编译器会输出警告)。 

我想这是设计者有意这么设计的,因为有时候我们就是要达到这种效果。严格的说,不能说通过使用new来实现多态,只能说在某些特定的时候碰巧实现了多态的效果。

 

3.3 要点:

a.多态是面向对象的重要特性之一,指同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。

b.多态分为两种:一种是编译时多态,使用重载实现;另一种是运行时多态,使用重写实现;

c.重写有两种,一种使用override关键词,另一种使用new关键词

d.new重写实际上是对父类方法的隐藏,被覆盖的父类方法可以调用得到。因此new可以重写(或说是隐藏)的父类方法不一定要定义为虚方法或抽象方法。只是如果父类方法是虚方法或抽象方法时会覆盖父类方法,如果不是,则隐藏。

e.重载和覆盖的发生条件:
重载,必然发生在一个类中,函数名相同,参数类型或者顺序不同构成重载,与返回类型无关
重写,必然发生在基类和派生类中,其类函数用virtual修饰,派生类用override修饰
覆盖,在子类中写一个和基类一样名字(参数不同也算)的非虚函数,会让基类中的函数被隐藏,编译后会提示要求使用New关键字 new 修饰 

隐藏,在子类中可以通过new 隐藏父类的方法

f.new覆盖与重写、重载的区别:

当子类与父类的参数不同时

当基类函数不是虚函数时,基类函数将被隐藏。(因为子类和基类不在同一范围内,所以不是重载)

当基类函数是虚函数时,基类函数将被隐藏。(因为子类和基类不在同一范围内,所以不是重载;因为参数不同,所以不是重写)

当子类与父类的参数相同时

当基类函数不是虚函数时,基类函数将被隐藏。(因为子类和基类不在同一范围内,所以不是重载,因为基类不是虚函数,所以是隐藏不是重写)

当基类函数是虚函数时,基类函数将被覆盖。(因为子类和基类不在同一范围内,所以不是重载)

==============================================================================================

返回目录

 <如果对你有帮助,记得点一下推荐哦,有不明白的地方或写的不对的地方,请多交流>

==============================================================================================