[.net 物件導向程式設計基礎] (13) 物件導向三大特性——多型

yubinfeng發表於2015-06-06

[.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覆蓋與重寫、過載的區別:

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

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

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

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

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

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

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

返回目錄

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

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

相關文章