[.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覆蓋與重寫、重載的區別:
當子類與父類的參數不同時
當基類函數不是虛函數時,基類函數將被隱藏。(因為子類和基類不在同一范圍內,所以不是重載)
當基類函數是虛函數時,基類函數將被隱藏。(因為子類和基類不在同一范圍內,所以不是重載;因為參數不同,所以不是重寫)
當子類與父類的參數相同時
當基類函數不是虛函數時,基類函數將被隱藏。(因為子類和基類不在同一范圍內,所以不是重載,因為基類不是虛函數,所以是隱藏不是重寫)
當基類函數是虛函數時,基類函數將被覆蓋。(因為子類和基類不在同一范圍內,所以不是重載)
==============================================================================================
返回目錄
<如果對你有幫助,記得點一下推薦哦,有不明白的地方或寫的不對的地方,請多交流>
==============================================================================================