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