[.net 面向對象編程基礎] (13) 面向對象三大特性——多態


[.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         do
22         {
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 MyName
100         {
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 : Animal
126     {
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 MyName
137         {
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 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         /// <summary>
59         /// 名字(重寫父類屬性)
60         /// </summary>
61         public override string MyName
62         {
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覆蓋與重寫、重載的區別:

當子類與父類的參數不同時

當基類函數不是虛函數時,基類函數將被隱藏。(因為子類和基類不在同一范圍內,所以不是重載)

當基類函數是虛函數時,基類函數將被隱藏。(因為子類和基類不在同一范圍內,所以不是重載;因為參數不同,所以不是重寫)

當子類與父類的參數相同時

當基類函數不是虛函數時,基類函數將被隱藏。(因為子類和基類不在同一范圍內,所以不是重載,因為基類不是虛函數,所以是隱藏不是重寫)

當基類函數是虛函數時,基類函數將被覆蓋。(因為子類和基類不在同一范圍內,所以不是重載)

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

返回目錄

 <如果對你有幫助,記得點一下推薦哦,有不明白的地方或寫的不對的地方,請多交流>

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM