<<展現(xiàn)C#>> 第五章 類(lèi)(rainbow 翻譯) 出處:http://www.informit.com\matter\ser0000002 正文: 第五章類(lèi) 前一章討論了數(shù)據(jù)類(lèi)型和它們的用法。現(xiàn)在我們轉(zhuǎn)移到C#中至關(guān)重要的結(jié)構(gòu)——類(lèi)。沒(méi)有了類(lèi),就連簡(jiǎn)單的C#程序都不能編譯。這一章假定你知道了一個(gè)類(lèi)的基本組成部分:方法、屬性、構(gòu)造函數(shù)和析構(gòu)函數(shù)。 C#在其中增加了索引和事件。 在這一章中,你學(xué)到下列有關(guān)類(lèi)的話題。 。 使用構(gòu)造函數(shù)和析構(gòu)函數(shù) 。給類(lèi)寫(xiě)方法 。給一個(gè)類(lèi)增加屬性存取標(biāo)志 。實(shí)現(xiàn)索引 。創(chuàng)建事件并通過(guò)代表元為事件關(guān)聯(lián)客戶 。應(yīng)用類(lèi)、成員和存取修飾符。
5.1構(gòu)造函數(shù)和析構(gòu)函數(shù) 在你可以訪問(wèn)一個(gè)類(lèi)的方法、屬性或任何其它東西之前, 第一條執(zhí)行的語(yǔ)句是包含有相應(yīng)類(lèi)的構(gòu)造函數(shù)。甚至你自己不寫(xiě)一個(gè)構(gòu)造函數(shù),也會(huì)有一個(gè)缺省的構(gòu)造函數(shù)提供給你。
class TestClass { public TestClass(): base() {} // 由編譯器提供 }
一個(gè)構(gòu)造函數(shù)總是和它的類(lèi)名相同,但是,它沒(méi)有聲明返回類(lèi)型。總之,構(gòu)造函數(shù)總是public的,你可以用它們來(lái)初始化變量。
public TestClass() { // 在這給變量 // 初始化代碼等等。 }
如果類(lèi)僅包含靜態(tài)成員(能以類(lèi)型調(diào)用,而不是以實(shí)例調(diào)用的成員),你可以創(chuàng)建一個(gè)private的構(gòu)造函數(shù)。 private TestClass() {} 盡管存取修飾符在這一章的后面將要大篇幅地討論,但是private意味著從類(lèi)的外面不可能訪問(wèn)該構(gòu)造函數(shù)。所以,它不能被調(diào)用,且沒(méi)有對(duì)象可以自該類(lèi)定義被實(shí)例化。 并不僅限于無(wú)參數(shù)構(gòu)造函數(shù)——你可以傳遞初始參數(shù)來(lái)初始化成員。 public TestClass(string strName, int nAge) { ... }
作為一個(gè)C/C++程序員,你可能習(xí)慣于給初始化寫(xiě)一個(gè)附加的方法,因?yàn)樵跇?gòu)造函數(shù)中沒(méi)有返回值。當(dāng)然,盡管在C#中也沒(méi)有返回值,但你可以引發(fā)一個(gè)自制的異常,以從構(gòu)造函數(shù)獲得返回值。更多有關(guān)異常處理的知識(shí)在第七章 "異常處理"中有討論。 但是,當(dāng)你保留引用給寶貴的資源,應(yīng)該想到寫(xiě)一個(gè)方法來(lái)解決:一個(gè)可以被顯式地調(diào)用來(lái)釋放這些資源。問(wèn)題是當(dāng)你可以在析構(gòu)函數(shù)(以類(lèi)名的前面加"~"的方式命名)中做同樣的事情時(shí),為何還要寫(xiě)一個(gè)附加的方法. public ~TestClass() { // 清除 }
你應(yīng)該寫(xiě)一個(gè)附加方法的原因是垃圾收集器,它在變量超出范圍后并不會(huì)立即被調(diào)用,而僅當(dāng)間歇期間或內(nèi)存條件滿足時(shí)才被觸發(fā)。當(dāng)你鎖住資源的時(shí)間長(zhǎng)于你所計(jì)劃的時(shí)間時(shí),它就會(huì)發(fā)生。因此,提供一個(gè)顯式的釋放方式是一個(gè)好主意,它同樣能從析構(gòu)函數(shù)中調(diào)用。
public void Release() { // 釋放所有寶貴的資源 }
public ~TestClass() { Release(); }
調(diào)用析構(gòu)函數(shù)中的釋放方法并不是必要的——總之,垃圾收集會(huì)留意釋放對(duì)象。但沒(méi)有忘記清除是一種良好的習(xí)慣。
5.2方法 既然對(duì)象能正確地初始化和結(jié)束,所剩下來(lái)的就是往類(lèi)中增加功能。在大多數(shù)情況下,功能的主要部分在方法中能得到實(shí)現(xiàn)。你早已見(jiàn)過(guò)靜態(tài)方法的使用,但是,這些是類(lèi)型(類(lèi))的部分,不是實(shí)例(對(duì)象)。 為了讓你迅速入門(mén),我把這些方法的煩瑣問(wèn)題安排為三節(jié): 。方法參數(shù) 。改寫(xiě)方法 。方法屏蔽 5.2.1方法參數(shù) 因方法要處理更改數(shù)值,你多多少少要傳遞值給方法,并從方法獲得返回值。以下三個(gè)部分涉及到由傳遞值和為調(diào)用者獲取返回結(jié)果所引起的問(wèn)題。
。輸入?yún)?shù) 。引用參數(shù) 。輸出參數(shù)
5.2.1.1輸入?yún)?shù) 你早已在例子中見(jiàn)過(guò)的一個(gè)參數(shù)就是輸入?yún)?shù)。你用一個(gè)輸入?yún)?shù)通過(guò)值傳遞一個(gè)變量給一個(gè)方法——方法的變量被調(diào)用者傳遞進(jìn)來(lái)的值的一個(gè)拷貝初始化。清單5.1 示范輸入?yún)?shù)的使用。
清單5.1 通過(guò)值傳遞參數(shù)
1: using System; 2: 3: public class SquareSample 4: { 5:public int CalcSquare(int nSideLength) 6:{ 7: return nSideLength*nSideLength; 8:} 9: } 10: 11: class SquareApp 12: { 13:public static void Main() 14:{ 15: SquareSample sq = new SquareSample(); 16: Console.WriteLine(sq.CalcSquare(25).ToString()); 17:} 18: }
因?yàn)槲覀鬟f值而不是引用給一個(gè)變量,所以當(dāng)調(diào)用方法時(shí)(見(jiàn)第16行),可以使用一個(gè)常量表達(dá)式(25)。整型結(jié)果被傳回給調(diào)用者作為返回值,它沒(méi)有存到中間變量就被立即顯示到屏幕上 。 輸入?yún)?shù)按C/C++程序員早已習(xí)慣的工作方式工作。如果你來(lái)自VB,請(qǐng)注意沒(méi)有能被編譯器處理的隱式ByVal或ByRef——如果沒(méi)有設(shè)定,參數(shù)總是用值傳遞。 這點(diǎn)似乎與我前面所陳述的有沖突:對(duì)于一些變量類(lèi)型,用值傳遞實(shí)際上意味著用引用傳遞。迷惑嗎? 一點(diǎn)背景知識(shí)也不需要:COM中的東西就是接口,每一個(gè)類(lèi)可以擁有一個(gè)或多個(gè)接口。一個(gè)接口只不過(guò)是一組函數(shù)指針,它不包含數(shù)據(jù)。重復(fù)該數(shù)組會(huì)浪費(fèi)很多內(nèi)存資源;所以,僅開(kāi)始地址被拷貝給方法,它作為調(diào)用者,仍然指向接口的相同指針。那就是為什么對(duì)象用值傳遞一個(gè)引用。
5.2.1.2引用參數(shù) 盡管可以利用輸入?yún)?shù)和返回值建立很多方法,但你一想到要傳遞值并原地修改它(也就是在相同的內(nèi)存位置),就沒(méi)有那么好運(yùn)了。這里用引用參數(shù)就很方便。 void myMethod(ref int nInOut) 因?yàn)槟銈鬟f了一個(gè)變量給該方法(不僅僅是它的值),變量必須被初始化。否則,編譯器會(huì)報(bào)警。清單 5.2 顯示如何用一個(gè)引用參數(shù)建立一個(gè)方法。
清單 5.2通過(guò)引用傳遞參數(shù)
1: // class SquareSample 2: using System; 3: 4: public class SquareSample 5: { 6:public void CalcSquare(ref int nOne4All) 7:{ 8: nOne4All *= nOne4All; 9:} 10: } 11: 12: class SquareApp 13: { 14:public static void Main() 15:{ 16: SquareSample sq = new SquareSample(); 17: 18: int nSquaredRef = 20; // 一定要初始化 19: sq.CalcSquare(ref nSquaredRef); 20: Console.WriteLine(nSquaredRef.ToString()); 21:} 22: }
正如所看到的,所有你要做的就是給定義和調(diào)用都加上ref限定符。因?yàn)樽兞客ㄟ^(guò)引用傳遞,你可以用它來(lái)計(jì)算出結(jié)果并傳回該結(jié)果。但是,在現(xiàn)實(shí)的應(yīng)用程序中,我強(qiáng)烈建議要用兩個(gè)變量,一個(gè)輸入?yún)?shù)和一個(gè)引用參數(shù)。
5.2.1.3輸出參數(shù) 傳遞參數(shù)的第三種選擇就是把它設(shè)作一個(gè)輸出參數(shù)。正如該名字所暗示,一個(gè)輸出參數(shù)僅用于從方法傳遞回一個(gè)結(jié)果。它和引用參數(shù)的另一個(gè)區(qū)別在于:調(diào)用者不必先初始化變量才調(diào)用方法。這顯示在清單5.3中。
清單5.3定義一個(gè)輸出參數(shù)
1: using System; 2: 3: public class SquareSample 4: { 5:public void CalcSquare(int nSideLength, out int nSquared) 6:{ 7: nSquared = nSideLength * nSideLength; 8:} 9: } 10: 11: class SquareApp 12: { 13:public static void Main() 14:{ 15: SquareSample sq = new SquareSample(); 16: 17: int nSquared; // 不必初始化 18: sq.CalcSquare(15, out nSquared); 19: Console.WriteLine(nSquared.ToString()); 20:} 21: }
5.2.2改寫(xiě)方法 面向?qū)ο笤O(shè)計(jì)的重要原則就是多態(tài)性。不要理會(huì)高深的理論,多態(tài)性意味著:當(dāng)基類(lèi)程序員已設(shè)計(jì)好用于改寫(xiě)的方法時(shí),在派生類(lèi)中,你就可以重定義(改寫(xiě))基類(lèi)的方法。基類(lèi)程序員可以用 virtual 關(guān)鍵字設(shè)計(jì)方法: virtual void CanBOverridden() 當(dāng)從基類(lèi)派生時(shí),所有你要做的就是在新方法中加入override關(guān)鍵字: override void CanBOverridden() 當(dāng)改寫(xiě)一個(gè)基類(lèi)的方法時(shí),你必須明白,不能改變方法的訪問(wèn)屬性——在這章的后面,你會(huì)學(xué)到更多關(guān)于訪問(wèn)修飾符的知識(shí)。 除了改寫(xiě)基類(lèi)方法的事實(shí)外,還有另一個(gè)甚至更重要的改寫(xiě)特性。當(dāng)把派生類(lèi)強(qiáng)制轉(zhuǎn)換成基類(lèi)類(lèi)型并接著調(diào)用虛擬方法時(shí),被調(diào)用的是派生類(lèi)的方法而不是基類(lèi)的方法。 ((BaseClass)DerivedClassInstance).CanBOverridden(); 為了演示虛擬方法的概念,清單 5.4 顯示如何創(chuàng)建一個(gè)三角形基類(lèi),它擁有一個(gè)可以被改寫(xiě)的成員方法(ComputeArea)。
清單 5.4 改寫(xiě)一個(gè)基類(lèi)的方法
1: using System; 2: 3: class Triangle 4: { 5:public virtual double ComputeArea(int a, int b, int c) 6:{ 7: // Heronian formula 8: double s = (a + b + c) / 2.0; 9: double dArea = Math.Sqrt(s*(s-a)*(s-b)*(s-c)); 10: return dArea; 11:} 12: } 13: 14: class RightAngledTriangle:Triangle 15: { 16:public override double ComputeArea(int a, int b, int c) 17:{ 18: double dArea = a*b/2.0; 19: return dArea; 20:} 21: } 22: 23: class TriangleTestApp 24: { 25:public static void Main() 26:{ 27: Triangle tri = new Triangle(); 28: Console.WriteLine(tri.ComputeArea(2, 5, 6)); 29: 30: RightAngledTriangle rat = new RightAngledTriangle(); 31: Console.WriteLine(rat.ComputeArea(3, 4, 5)); 32:} 33: }
基類(lèi)Triangle定義了方法ComputeArea。它采用三個(gè)參數(shù),返回一個(gè)double結(jié)果,且具有公共訪問(wèn)性。從Triangle類(lèi)派生出的是RightAngledTriangle,它改寫(xiě)了ComputeArea 方法,并實(shí)現(xiàn)了自己的面積計(jì)算公式。兩個(gè)類(lèi)都被實(shí)例化,且在命名為T(mén)riangleTestApp的應(yīng)用類(lèi)的Main() 方法中得到驗(yàn)證。 我漏了解釋第14行: class RightAngledTriangle : Triangle 在類(lèi)語(yǔ)句中冒號(hào)(:)表示RightAngledTriangle從類(lèi) Triangle派生。那就是你所必須要做的,以讓C#知道你想把 Triangle當(dāng)作RightAngledTriangle的基類(lèi)。 當(dāng)仔細(xì)觀察直角三角形的ComputeArea方法時(shí),你會(huì)發(fā)現(xiàn)第3個(gè)參數(shù)并沒(méi)有用于計(jì)算。但是,利用該參數(shù)就可以驗(yàn)證是否是“直角”。如清單5.5所示。
清單 5.5 調(diào)用基類(lèi)實(shí)現(xiàn)
1: class RightAngledTriangle:Triangle 2: { 3:public override double ComputeArea(int a, int b, int c) 4:{ 5: const double dEpsilon = 0.0001; 6: double dArea = 0; 7: if (Math.Abs((a*a + b*b - c*c)) > dEpsilon) 8: { 9:dArea = base.ComputeArea(a,b,c); 10: } 11: else 12: { 13:dArea = a*b/2.0; 14: } 15: 16: return dArea; 17:} 18: }
該檢測(cè)簡(jiǎn)單地利用了畢達(dá)哥拉斯公式,對(duì)于直角三角形,檢測(cè)結(jié)果必須為0。如果結(jié)果不為0,類(lèi)就調(diào)用它基類(lèi)的 ComputeArea來(lái)實(shí)現(xiàn)。 dArea = base.ComputeArea(a,b,c); 例子的要點(diǎn)為:通過(guò)顯式地利用基類(lèi)的資格檢查,你就能輕而易舉地調(diào)用基類(lèi)實(shí)現(xiàn)改寫(xiě)方法。 當(dāng)你需要實(shí)現(xiàn)其在基類(lèi)中的功能,而不愿意在改寫(xiě)方法中重復(fù)它時(shí),這就非常有幫助。
5.2.3 方法屏蔽 重定義方法的一個(gè)不同手段就是要屏蔽基類(lèi)的方法。當(dāng)從別人提供的類(lèi)派生類(lèi)時(shí),這個(gè)功能特別有價(jià)值。看清單 5.6,假設(shè)BaseClass由其他人所寫(xiě),而你從它派生出 DerivedClass 。
清單 5.6 Derived Class 實(shí)現(xiàn)一個(gè)沒(méi)有包含于 Base Class中的方法
1: using System; 2: 3: class BaseClass 4: { 5: } 6: 7: class DerivedClass:BaseClass 8: { 9:public void TestMethod() 10:{ 11: Console.WriteLine("DerivedClass::TestMethod"); 12:} 13: } 14: 15: class TestApp 16: { 17:public static void Main() 18:{ 19: DerivedClass test = new DerivedClass(); 20: test.TestMethod(); 21:} 22: }
在這個(gè)例子中, DerivedClass 通過(guò)TestMethod()實(shí)現(xiàn)了一個(gè)額外的功能。但是,如果基類(lèi)的開(kāi)發(fā)者認(rèn)為把TestMethod()放在基類(lèi)中是個(gè)好主意,并使用相同的名字實(shí)現(xiàn)它時(shí),會(huì)出現(xiàn)什么問(wèn)題呢?(見(jiàn)清單5.7)
清單 5.7Base Class 實(shí)現(xiàn)和 Derived Class相同的方法
1: class BaseClass 2: { 3:public void TestMethod() 4:{ 5: Console.WriteLine("BaseClass::TestMethod"); 6:} 7: } 8: 9: class DerivedClass:BaseClass 10: { 11:public void TestMethod() 12:{ 13: Console.WriteLine("DerivedClass::TestMethod"); 14:} 15: }
在優(yōu)秀的編程語(yǔ)言中,你現(xiàn)在會(huì)遇到一個(gè)真正的大麻煩。但是,C#會(huì)給你提出警告: hiding2.cs(13,14): warning CS0114: 'DerivedClass.TestMethod()' hides inherited member 'BaseClass.TestMethod()'. To make the current method override that implementation, add the override keyword. Otherwise add the new keyword. (hiding2.cs(13,14):警告CS0114:'DerivedClass.TestMethod()' 屏蔽了所繼承的成員'BaseClass.TestMethod()'。要想使當(dāng)前方法改寫(xiě)原來(lái)的實(shí)現(xiàn),加上 override關(guān)鍵字。否則加上新的關(guān)鍵字。) 具有了修飾符new,你就可以告訴編譯器,不必重寫(xiě)派生類(lèi)或改變使用到派生類(lèi)的代碼,你的方法就能屏蔽新加入的基類(lèi)方法。清單5.8顯示如何在例子中運(yùn)用new修飾符。
清單5.8 屏蔽基類(lèi)方法
1: class BaseClass 2: { 3:public void TestMethod() 4:{ 5: Console.WriteLine("BaseClass::TestMethod"); 6:} 7: } 8: 9: class DerivedClass:BaseClass 10: { 11:new public void TestMethod() 12:{ 13: Console.WriteLine("DerivedClass::TestMethod"); 14:} 15: }
使用了附加的new修飾符,編譯器就知道你重定義了基類(lèi)的方法,它應(yīng)該屏蔽基類(lèi)方法。但是,如果你按以下方式編寫(xiě): DerivedClass test = new DerivedClass(); ((BaseClass)test).TestMethod(); 基類(lèi)方法的實(shí)現(xiàn)就被調(diào)用了。這種行為不同于改寫(xiě)方法,后者保證大部分派生方法
|