C#面向?qū)ο蟾呒壘幊蘝第1頁
C#面向?qū)ο蟾呒壘幊蘝第2頁
C#面向?qū)ο蟾呒壘幊蘝第3頁
C#面向?qū)ο蟾呒壘幊蘝第4頁
C#面向?qū)ο蟾呒壘幊蘝第5頁
已閱讀5頁,還剩206頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

第4章C#面向?qū)ο蟾呒壘幊?/p>

前面介紹了面向?qū)ο蟪绦蛟O(shè)計的基本概念和應(yīng)用。但是面向?qū)ο筮€包括很多其他重要的概念。本章將深入分析面向?qū)ο缶幊痰母拍睿⒃敿?xì)說明利用工具進(jìn)行面向?qū)ο蟪绦蛟O(shè)計的方法。

4.1類的繼承與多態(tài)

4.1.1繼承1、概述現(xiàn)實(shí)世界中的許多實(shí)體之間不是相互孤立的,它們往往具有共同的特征,也存在內(nèi)在的差別。人們可以采用層次結(jié)構(gòu)來描述這些實(shí)體之間的相似之處和不同之處。為了用軟件語言對現(xiàn)實(shí)世界中的層次結(jié)構(gòu)進(jìn)行模型化,面向?qū)ο蟮某绦蛟O(shè)計技術(shù)引入了繼承的概念。一個類從另一個類派生出來時,派生類從基類那里繼承特性。派生類也可以作為其它類的基類。從一個基類派生出來的多層類形成了類的層次結(jié)構(gòu)。注意:C#中,派生類只能從一個類中繼承。這是因?yàn)?,在C++中,人們在大多數(shù)情況下不需要一個從多個類中派生的類。從多個基類中派生一個類,這往往會帶來許多問題,從而抵消了這種靈活性帶來的優(yōu)勢。C#中,派生類從它的直接基類中繼承成員:方法、域、屬性、事件、索引指示器。除了構(gòu)造函數(shù)和析構(gòu)函數(shù),派生類隱式地繼承了直接基類的所有成員。C#中,派生類從它的直接基類中繼承成員:方法、域、屬性、事件、索引指示器。除了構(gòu)造函數(shù)和析構(gòu)函數(shù),派生類隱式地繼承了直接基類的所有成員。程序清單:usingSystem;namespaceConsoleApplication1{ classVehicle//定義汽車類

{

intwheels;//公有成員:輪子個數(shù)

protectedfloatweight;//保護(hù)成員:重量

publicVehicle(){;} publicVehicle(intw,floatg) { wheels=w; weight=g;} publicvoidSpeak() {

Console.WriteLine("thewvehicleisspeaking!"); }} classCar:Vehicle//定義轎車類:從汽車類中繼承

{intpassengers;//私有成員:乘客數(shù)

publicCar(intw,floatg,intp):base(w,g) {wheels=w; weight=g; passengers=p; } }}Vehicle作為基類,體現(xiàn)了“汽車”這個實(shí)體具有的公共性質(zhì):汽車都有輪子和重量。Car類繼承了Vehicle的這些性質(zhì),并且添加了自身的特性:可以搭載乘客。

C#中的繼承符合下列規(guī)則:(1)繼承是可傳遞的。如果C從B中派生,B又從A中派生,那么C不僅繼承了B中聲明的成員,同樣也繼承了A中的成員。Object類作為所有類的基類。(2)派生類應(yīng)當(dāng)是對基類的擴(kuò)展。派生類可以添加新的成員,但不能除去己經(jīng)繼承的成員的定義。(3)構(gòu)造函數(shù)和析構(gòu)函數(shù)不能被繼承。除此以外的其它成員,不論對它們定義了怎樣的訪問方式,都能被繼承?;愔谐蓡T的訪問方式只能決定派生類能否訪問它們。(4)派生類如果定義了與繼承而來的成員同名的新成員,就可以覆蓋己繼承的成員。但這并不因?yàn)檫@派生類刪除了這些成員,只是不能再訪問這些成員。(5)類可以定義虛方法、虛屬性以及虛索引指示器,它的派生類能夠重載這些成員,從而實(shí)現(xiàn)類可以展示出多態(tài)性。2、覆蓋我們上面提到,類的成員聲明中,可以聲明與繼承而來的成員同名的成員。這時我們稱派生類的成員覆蓋(hide)了基類的成員。這種情況下,編譯器不會報告錯誤,但會給出一個警告。對派生類的成員使用new關(guān)鍵字,可以關(guān)閉這個警告。前面汽車類的例子中,類Car繼承了Vehicle的Speak()方法。我們可以給Car類也聲明一個Speak()方法,覆蓋Vehicle中的Speak,見下面的代碼。程序清單:usingSystem;

namespaceConsoleApplication1

{ classVehicle//定義汽車類

{ publicintwheels;//公有成員:輪子個數(shù)

protectedfloatweight;//保護(hù)成員:重量

publicVehicle(){;} publicVehicle(intw,floatg) {wheels=w;

weight=g;} publicvoidSpeak() {

Console.WriteLine("thewvehicleisspeaking!"); } } classCar:Vehicle//定義轎車類

{

intpassengers;//私有成員:乘客數(shù)

publicCar(intw,floatg,intp) { wheels=w; weight=g;passengers=p;} newpublicvoidSpeak() {

Console.WriteLine("Di-di!"); } }}

注意:如果在成員聲明中加上了new關(guān)鍵字修飾,而該成員事實(shí)上并沒有覆蓋繼承的成員,編譯器將會給出警告。在個成員聲明同時使用new和override則編譯器會報告錯誤。3、base保留字base關(guān)鍵字主要是為派生類調(diào)用基類成員提供一個簡寫的方法。我們先看一個例子程序代碼:usingSystem;namespaceConsoleApplication1{ classA { publicvoidF() { //F的具體執(zhí)行代碼

}

publicint

this[int

nIndex]

{ get{} set{} } classB { publicvoidG() {

intx=base[0];

base.F(); } } }}類B從類A中繼承,B的方法G中調(diào)用了A的方法F和索引指示器。方法F在進(jìn)行編譯時等價于:publicvoidG(){

intx=(A(this))[0];(A(this)).F();}使用base關(guān)鍵字對基類成員的訪問格式為:base.標(biāo)識符base[表達(dá)式列表]

4.1.2多態(tài)

在面向?qū)ο蟮南到y(tǒng)中,多態(tài)性是一個非常重要的概念,它允許客戶對一個對象進(jìn)行操作,由對象來完成一系列的動作,具體實(shí)現(xiàn)哪個動作、如何實(shí)現(xiàn)由系統(tǒng)負(fù)責(zé)解釋。

C#中的多態(tài)“多態(tài)性”一詞最早用于生物學(xué),指同一種族的生物體具有相同的特性。在C#中,多態(tài)性的定義是:同一操作作用于不同的類的實(shí)例,不同的類將進(jìn)行不同的解釋,最后產(chǎn)生不同的執(zhí)行結(jié)果。C#支持兩種類型的多態(tài)性:(1)編譯時的多態(tài)性編譯時的多態(tài)性是通過重載來實(shí)現(xiàn)的。我們在前面介紹了方法重載,都實(shí)現(xiàn)了編譯時的多態(tài)性。對于非虛的成員來說,系統(tǒng)在編譯時,根據(jù)傳遞的參數(shù)、返回的類型等信急決定實(shí)現(xiàn)何種操作。(2)運(yùn)行時的多態(tài)性運(yùn)行時的多態(tài)性就是指直到系統(tǒng)運(yùn)行時,才根據(jù)實(shí)際情況決定實(shí)現(xiàn)何種操作。C#中,運(yùn)行時的多態(tài)性通過虛成員實(shí)現(xiàn)。編譯時的多態(tài)性為我們提供了運(yùn)行速度快的特點(diǎn),而運(yùn)行時的多態(tài)性則帶來了高度靈活和抽象的特點(diǎn)。

虛方法

當(dāng)類中的方法聲明前加上了virtual修飾符,我們稱之為虛方法,反之為非虛。使用了virtual修飾符后,不允許再有static,abstract,或override修飾符。對于非虛的方法,無論被其所在類的實(shí)例調(diào)用,還是被這個類的派生類的實(shí)例調(diào)用,方法的執(zhí)行方式不變。而對于虛方法,它的執(zhí)行方式可以被派生類改變,這種改變是通過方法的重載來實(shí)現(xiàn)的。案例:虛方法與非虛方法的調(diào)用目標(biāo):說明了虛方法與非虛方法的區(qū)別步驟:1、啟動VS.NET,新建一個控制臺應(yīng)用程序,名稱填寫為“DifferentiateTest”,位置設(shè)置為“c:\CSharpSamples\chp4”。2、在代碼設(shè)計窗口中編輯Class1.cs。其中的代碼編寫如下:

usingSystem;namespaceDifferentiateTest{classA {publicvoidF(){Console.WriteLine("A.F");} publicvirtualvoidG(){Console.WriteLine("A.G");} } classB:A {newpublicvoidF(){Console.WriteLine("B.F");} publicoverridevoidG(){Console.WriteLine("B.G");} }

classTest{ staticvoidMain() { Bb=newB(); Aa=b;

a.F();

b.F();

a.G();

b.G(); } }}3、按Ctrl+F5編譯并運(yùn)行該程序,效果如圖5-2所示。圖4-1程序運(yùn)行結(jié)果例子中,A類提供了兩個方法:非虛的F和虛方法G。類B則提供了一個新的非虛的方法F,從而覆蓋了繼承的F;類B同時還重載了繼承的方法G。

在本例中,方法a.G()實(shí)際調(diào)用了B.G,而不是A.G。這是因?yàn)榫幾g時值為A,但運(yùn)行時值為B,所以B完成了對方法的實(shí)際調(diào)用。

在派生類中對虛方法進(jìn)行重載

先讓我們回顧一下普通的方法重載。普通的方法重載指的是:類中兩個以上的方法(包括隱藏的繼承而來的方法),取的名字相同,只要使用的參數(shù)類型或者參數(shù)個數(shù)不同,編譯器便知道在何種情況下應(yīng)該調(diào)用哪個方法。而對基類虛方法的重載是函數(shù)重載的另一種特殊形式。在派生類中重新定義此虛函數(shù)時,要求的是方法名稱、返回值類型、參數(shù)表中的參數(shù)個數(shù)、類型、順序都必須與基類中的虛函數(shù)完全一致。在派生類中聲明對虛方法的重載,要求在聲明中加上override關(guān)鍵字,而且不能有new,static或virtual修飾符。4.1.3抽象和密封

1、抽象類有時候,基類并不與具體的事物相聯(lián)系,而是只表達(dá)一種抽象的概念,用以為它的派生類提供一個公共的界面。為此,C#中引入了抽象類(abstractclass)的概念。注意:C++程序員在這里最容易犯錯誤。C++中沒有對抽象類進(jìn)行直接聲明的方法,而認(rèn)為只要在類中定義了純虛函數(shù),這個類就是一個抽象類。純虛函數(shù)的概念比較晦澀,直觀上不容易為人們接受和掌握,因此C#拋棄了這一概念。抽象類使用abstract修飾符,對抽象類的使用有以下幾點(diǎn)規(guī)定:(1)抽象類只能作為其它類的基類,它不能直接被實(shí)例化,而且對抽象類不能使用new操作符。抽象類如果含有抽象的變量或值,則它們要么是null類型,要么包含了對非抽象類的實(shí)例的引用。(2)抽象類允許包含抽象成員,雖然這不是必須的。(3)抽象類不能同時又是密封的。

如果一個非抽象類從抽象類中派生,則其必須通過重載來實(shí)現(xiàn)所有繼承而來的抽象成員。請看下面的示例:usingSystem;namespaceConsoleApplication1{ abstractclassA { publicabstractvoidF(); } abstractclassB:A { publicvoidG(){} }classC:B

{

publicoverridevoidFD

{

//F的具體實(shí)現(xiàn)代碼

}

}

}抽象類A提供了一個抽象方法F。類B從抽象類A中繼承,并且又提供了一個方法G;因?yàn)锽中并沒有包含對F的實(shí)現(xiàn),所以B也必須是抽象類。類C從類B中繼承,類中重載了抽象方法F,并且提供了對F的具體實(shí)現(xiàn),則類C允許是非抽象的。

2、抽象方法由于抽象類本身表達(dá)的是抽象的概念,因此類中的許多方法并不一定要有具體的實(shí)現(xiàn),而只是留出一個接日來作為派生類重載的界面。舉一個簡單的例子,“圖形”這個類是抽象的,它的成員方法“計算圖形面積”也就沒有實(shí)際的意義。面積只對“圖形”的派生類比如“圓”、“一角形”這些非抽象的概念才有效,那么我們就可以把基類“圖形”的成員方法“計算面積”聲明為抽象的,具體的實(shí)現(xiàn)交給派生類通過重載來實(shí)現(xiàn)。一個方法聲明中如果加上abstract修飾符,我們稱該方法為抽象方法(abstractmethod)。如果一個方法被聲明也是抽象的,那么該方法默認(rèn)也是一個虛方法。事實(shí)上,抽象方法是一個新的虛方法,它不提供具體的方法實(shí)現(xiàn)代碼。我們知道,非虛的派生類要求通過重載為繼承的虛方法提供自己的實(shí)現(xiàn),而抽象方法則不包含具體的實(shí)現(xiàn)內(nèi)容,所以方法聲明的執(zhí)行體中只有一個分號“;”。只能在抽象類中聲明抽象方法。對抽象方法,不能再使用static或virtual修飾符,而且方法不能有任何可執(zhí)行代碼,哪怕只是一對大括號中間加一個一個分號“{;}”都不允許出現(xiàn),只需要給出方法的原型就可以了。還要注意,抽象方法在派生類中不能使用base關(guān)鍵字來進(jìn)行訪問。例如,下面的代碼在編譯時會發(fā)生錯誤:usingSystem;namespaceConsoleApplication1{classA {publicabstractvoidF(); } classB:A {publicoverridevoidF()

{

base.F();//錯誤,base.F是抽象方法

}

}

}我們還可以利用抽象方法來重載基類的虛方法,這時基類中虛方法的執(zhí)行代碼就被“攔截”了。下面的例子說明了這一點(diǎn):usingSystem;namespaceConsoleApplication1{classA {publicvirtualvoidF(){

Console.WriteLine("A.F"); } } abstractclassB:A { publicabstractoverridevoidF(); } classC:B { publicoverridevoidF() {

Console.WriteLine("C.F"); } }}類A聲明了一個虛方法F,派生類B使用抽象方法重載了F,這樣B的派生類C就可以重載F并提供自己的實(shí)現(xiàn)。3、密封類想想看,如果所有的類都可以被繼承,繼承的濫用會帶來什么后果?類的層次結(jié)構(gòu)體系將變得十分龐大,類之間的關(guān)系雜亂無章,對類的理解和使用都會變得十分困難。有時候,我們并不希望自己編寫的類被繼承。另一些時候,有的類己經(jīng)沒有再被繼承的必要。C#提出了一個密封類(sealedclass)的概念,幫助開發(fā)人員來解決這一問題。密封類在聲明中使用sealed修飾符,這樣就可以防止該類被其它類繼承。如果試圖將一個密封類作為其它類的基類,C#將提不出錯。理所當(dāng)然,密封類不能同時又是抽象類,因?yàn)槌橄罂偸窍M焕^承的。在哪些場合下使用密封類呢?密封類可以阻止其它程序員在無意中繼承該類,而且密封類可以起到運(yùn)行時優(yōu)化的效果。實(shí)際上,密封類中不可能有派生類,如果密封類實(shí)例中存在虛成員函數(shù),該成員函數(shù)可以轉(zhuǎn)化為非虛的,函數(shù)修飾符virtual不再生效。讓我們看下面的例子:usingSystem;namespaceConsoleApplication1{abstractclassA {publicabstractvoidF(); } sealedclassB:A {publicoverridevoidF()

{

//F的具體實(shí)現(xiàn)代碼

}

}

}如果我們嘗試寫下面的代碼:classC:B{}C#會指出這個錯誤,告訴你B是一個密封類,不能試圖從B中派生任何類。4、密封方法我們己經(jīng)知道,使用密封類可以防止對類的繼承。C#還提出了密封方法(sealedmethod)的概念,以防止在方法所在類的派生類中對該方法的重載。對方法可以使用sealed修飾符,這時我們稱該方法是一個密封方法。不是類的每個成員方法都可以作為密封方法,密封方法必須對基類的虛方法進(jìn)行重載,提供具體的實(shí)現(xiàn)方法。所以,在方法的聲明中,sealed修飾符總是和override修飾符同時使用。請看例子代碼。程序清單:usingSystem;namespaceConsoleApplication1{ classA { publicvirtualvoidF() {

Console.WriteLine("A.F");

} publicvirtualvoidG() {Console.WriteLine("A.G"); } } classB:A {sealedoverridepublicvoidF() {Console.WriteLine("B.F"); } overridepublicvoidG() {Console.WriteLine("B.G"); } } classC:B {overridepublicvoidG()

{

Console.WriteLine("C.G");

}

}

}類B對基類A中的兩個虛方法均進(jìn)行了重載,其中F方法使用了Sealed修飾符,成為一個密封方法。G方法不是密封方法,所以在B的派生類C中,可以重載方法G,但不能重載方法F。

4.2操作符重載

4.2.1問題的提出在面向?qū)ο蟮某绦蛟O(shè)計中,自己定義一個類,就等于創(chuàng)建了一個新類型。類的實(shí)例和變量一樣,可以作為參數(shù)傳遞,也可以作為返回類型。在前幾章中,我們介紹了系統(tǒng)定義的許多操作符。比如對于兩個整型變量,使用算術(shù)操作符可以簡便地進(jìn)行算術(shù)運(yùn)算:classA { publicintx; publicinty; publicintPlus() { returnx+y; } }再比如,我們希望將屬于不同類的兩個實(shí)例的數(shù)據(jù)內(nèi)容相加:classB {publicintx; } classTest {publicintz; publicstaticvoidMain() { Aa=newA(); Bb=newB(); z=a.x+b.x; } }使用a.x+b.x這種寫法不夠簡潔,也不夠直觀。更為嚴(yán)重的問題是,如果類的成員在聲明時使用的不是public修飾符的話,這種訪問就是非法的。

我們知道,在C#中,所有數(shù)據(jù)要么屬于某個類,要么屬于某個類的實(shí)例,充分體現(xiàn)了面向?qū)ο蟮乃枷?。因此,為了表達(dá)上的方便,人們希望可以重新給己定義的操作符賦予新的含義,在特定的類的實(shí)例上進(jìn)行新的解釋。這就需要通過操作符重載來解決。4.2.2使用成員方法重載操作符

C#中,操作符重載總是在類中進(jìn)行聲明,并且通過調(diào)用類的成員方法來實(shí)現(xiàn)。操作符重載聲明的格式為:typeoperatoroperator-game(formal-param-list)

C#中,下列操作符都是可以重載的:

+-!~++--truefalse

*/%&|^<<>>==!=><>=<=但也有一些操作符是不允許進(jìn)行重載的,如:

=,&&,||,?:,new,typeof,sizeof,is◆一元操作符重載顧名思義,一元操作符重載時操作符只作用于一個對象,此時參數(shù)表為空,當(dāng)前對象作為操作符的單操作數(shù)。下面我們舉一個角色類游戲中經(jīng)常遇到的例子。扮演的角色具有內(nèi)力、體力、經(jīng)驗(yàn)值、剩余體力、剩余內(nèi)力五個屬性,每當(dāng)經(jīng)驗(yàn)值達(dá)到一定程度時,角色便會升級,體力、內(nèi)力上升,剩余體力和內(nèi)力補(bǔ)滿。“升級”我們使用重載操作符“++”來實(shí)現(xiàn)。案例:游戲中“升級”問題目標(biāo):掌握一元操作符重載的基本方法步驟:1、啟動VS.NET,新建一個控制臺應(yīng)用程序,名稱填寫為“PlayerTest”,位置設(shè)置為“c:\CSharpSamples\chp4”。2、在代碼設(shè)計窗口中編輯Class1.cs。其中的代碼編寫如下:usingSystem;

namespacePlayerTest{classPlayer {publicint

neili; publicint

tili; publicint

jingyan; publicint

neili_r; publicint

tili_r; publicPlayer() {neili=10;

tili=50;

jingyan=0;

neili_r=50;

tili_r=50; } publicstaticPlayeroperator++(Playerp) {

p.neili=p.neili+50;

p.tili=p.tili+100;

p.neili_r=p.neili;

p.tili_r=p.tili;

returnp;

}publicvoidShow() {Console.WriteLine("Tili:{0}",tili);

Console.WriteLine("Jingyan:{0}",jingyan); Console.WriteLine("Neili{0}",neili);

Console.WriteLine("Tili_full:{0}",tili_r);

Console.WriteLine("Neili_full:{0}",neili_r); } }classTest

{

publicstaticvoidMain()

{

Playerman=newPlayer();

man.Show();

man++;

Console.WriteLine("Nowupgrading…:");

man.Show();

}

}

}3、按Ctrl+F5編譯并運(yùn)行該程序,效果如圖4-2所示?!舳僮鞣剌d大多數(shù)情況下我們使用二元操作符重載。這時參數(shù)表中有一個參數(shù),當(dāng)前對象作為該操作符的左操作數(shù),參數(shù)作為操作符的右操作數(shù)。圖4-2程序運(yùn)行結(jié)果下面我們給出二元操作符重載的一個簡單例子。

案例:笛卡兒坐標(biāo)相加問題

目標(biāo):掌握二元操作符重載的基本方法

步驟:1、啟動VS.NET,新建一個控制臺應(yīng)用程序,名稱填寫為“DKRTest”,位置設(shè)置為“c:\CSharpSamples\chp4”。2、在代碼設(shè)計窗口中編輯Class1.cs。其中的代碼編寫如下:usingSystem;namespaceDKRTest{classDKR {publicint

x,y,z; publicDKR(int

vx,int

vy,int

vz) {

x=vx;

y=vy;

z=vz;

}publicstaticDKRoperator+(DKRd1,DKRd2) {DKRdkr=newDKR(0,0,0);

dkr.x=d1.x+d2.x;

dkr.y=d1.y+d2.y;

dkr.z=d1.z+d2.z; returndkr; } } classTest {publicstaticvoidMain() {DKRd1=newDKR(3,2,1);

DKRd2=newDKR(0,6,5);

DKRd3=d1+d2;

Console.WriteLine("The3dlocationofd3is:{0},{1},{2}",d3.x,d3.y,d3.z);

}

}

}3、按Ctrl+F5編譯并運(yùn)行該程序,效果如圖4-3所示圖4-3程序運(yùn)行結(jié)果

4.3類型轉(zhuǎn)換

轉(zhuǎn)換是使一種類型的表達(dá)式可以被視為另一種類型。轉(zhuǎn)換可以是隱式或顯式,這將確定是否需要顯式地強(qiáng)制轉(zhuǎn)換。例如,從int類型到long類型的轉(zhuǎn)換是隱式的,因此int類型的表達(dá)式可隱式地按long類型處理。從long類型到int類型的反向轉(zhuǎn)換是顯式的,因此需要顯式地強(qiáng)制轉(zhuǎn)換。

inta=123;longb=a;//從int

類型到long類型的轉(zhuǎn)換

intc=(int)b;//從long類型到int

類型的反向轉(zhuǎn)換4.3.1隱式類型轉(zhuǎn)換

下列轉(zhuǎn)換屬于隱式轉(zhuǎn)換:◆標(biāo)識轉(zhuǎn)換◆隱式數(shù)值轉(zhuǎn)換◆隱式枚舉轉(zhuǎn)換◆隱式常數(shù)表達(dá)式轉(zhuǎn)換◆用戶定義的隱式轉(zhuǎn)換隱式轉(zhuǎn)換可以在各種情況下發(fā)生,包括函數(shù)成員調(diào)用、強(qiáng)制轉(zhuǎn)換表達(dá)式以及賦值。1、標(biāo)識轉(zhuǎn)換是在同一類型(可為任何類型)內(nèi)進(jìn)行轉(zhuǎn)換。這種轉(zhuǎn)換的存在,僅僅是為了使已具有所需類型的實(shí)體可被認(rèn)為是可轉(zhuǎn)換的(轉(zhuǎn)換為該類型)。2、隱式數(shù)值轉(zhuǎn)換為:◆從sbyte到short、int、long、float、double或decimal。

◆從byte到short、ushort、int、uint、long、ulong、floatdouble或decimal。

◆從short到int、long、float、double或decimal。

◆從ushort到int、uint、long、ulong、float、double或decimal。

◆從int到long、float、double或decimal。

◆從uint到long、ulong、float、double或decimal。

◆從long到float、double或decimal。

◆從ulong到float、double或decimal。

◆從char到ushort、int、uint、long、ulong、float、double或decimal。

◆從float到double。從int、uint、long或ulong到float以及從long或ulong到double的轉(zhuǎn)換可能導(dǎo)致精度損失,但決不會影響到它的數(shù)量級。其他的隱式數(shù)值轉(zhuǎn)換決不會丟失任何信息。不存在向char類型的隱式轉(zhuǎn)換,因此其它整型的值不會自動轉(zhuǎn)換為char類型。

3、隱式枚舉轉(zhuǎn)換允許將十進(jìn)制整數(shù)0轉(zhuǎn)換為任何枚舉類型。

4、隱式常數(shù)表達(dá)式轉(zhuǎn)換允許進(jìn)行以下轉(zhuǎn)換:

◆int類型的常數(shù)表達(dá)式可以轉(zhuǎn)換為sbyte、byte、short、ushort、uint或ulong類型(前提是常數(shù)表達(dá)式的值在目標(biāo)類型的范圍內(nèi))?!鬺ong類型的常數(shù)表達(dá)式可以轉(zhuǎn)換為ulong類型(前提是常數(shù)表達(dá)式的值不為負(fù))。5、用戶定義的隱式轉(zhuǎn)換由以下三部分組成:先是一個標(biāo)準(zhǔn)的隱式轉(zhuǎn)換(可選);然后是執(zhí)行用戶定義的隱式轉(zhuǎn)換運(yùn)算符,最后是另一個標(biāo)準(zhǔn)的隱式轉(zhuǎn)換(可選)。計算用戶定義的轉(zhuǎn)換的精確規(guī)則的說明。從S類型到T類型的用戶定義的隱式轉(zhuǎn)換按下面這樣處理:1)查找類型集D,將從該類型集考慮用戶定義的轉(zhuǎn)換運(yùn)算符。此集合由S(如果S是類或構(gòu)造)、S的基類(如果S是類)和T(如果T是類或結(jié)構(gòu))組成。2)查找適用的用戶定義轉(zhuǎn)換運(yùn)算符集合U。集合U由用戶定義的隱式轉(zhuǎn)換運(yùn)算符組成,這些運(yùn)算符是在D中的類或結(jié)構(gòu)內(nèi)聲明的,用于從包含S的類型轉(zhuǎn)換為被T包含的類型。如果U為空,則轉(zhuǎn)換未定義并且發(fā)生編譯時錯誤。3)在U中查找運(yùn)算符的最精確的源類型SX:①如果U中的所有運(yùn)算符都從S轉(zhuǎn)換,則SX為S。②否則,SX在U中運(yùn)算符的合并目標(biāo)類型集中是被包含程度最大的類型。如果找不到這樣的被包含程度最大的類型,則轉(zhuǎn)換是不明確的,并且發(fā)生編譯時錯誤。

4)在U中查找運(yùn)算符的最精確的目標(biāo)類型TX:①如果U中的所有運(yùn)算符都轉(zhuǎn)換為T,則TX為T。②否則,TX在U中運(yùn)算符的合并目標(biāo)類型集中是包含程度最大的類型。如果找不到這樣的包含程度最大的類型,則轉(zhuǎn)換是不明確的,并且發(fā)生編譯時錯誤。5)如果U中正好含有一個從SX轉(zhuǎn)換到TX的用戶定義轉(zhuǎn)換運(yùn)算符,則這就是最精確的轉(zhuǎn)換運(yùn)算符。如果不存在此類運(yùn)算符,或者如果存在多個此類運(yùn)算符,則轉(zhuǎn)換是不明確的,并且發(fā)生編譯時錯誤。否則,將應(yīng)用用戶定義的轉(zhuǎn)換:①如果S不是SX,則先執(zhí)行一個從S到SX的標(biāo)準(zhǔn)隱式轉(zhuǎn)換。②調(diào)用最精確的用戶定義轉(zhuǎn)換運(yùn)算符,以從SX轉(zhuǎn)換到TX。③如果TX不是T,則再執(zhí)行一個TX到T的標(biāo)準(zhǔn)隱式轉(zhuǎn)換。4.3.2顯式類型轉(zhuǎn)換

下列轉(zhuǎn)換屬于顯式轉(zhuǎn)換:◆顯式數(shù)值轉(zhuǎn)換◆顯式枚舉轉(zhuǎn)換◆用戶定義的顯式轉(zhuǎn)換顯式轉(zhuǎn)換可在強(qiáng)制轉(zhuǎn)換表達(dá)式中出現(xiàn)。顯式轉(zhuǎn)換集包括所有隱式轉(zhuǎn)換。這意味著允許使用冗余的強(qiáng)制轉(zhuǎn)換表達(dá)式。不是隱式轉(zhuǎn)換的顯式轉(zhuǎn)換是這樣的一類轉(zhuǎn)換:它們不能保證總是成功,知道有可能丟失信息,變換前后的類型顯著不同,以至值得使用顯式表示法。1、顯式數(shù)值轉(zhuǎn)換是指從一個數(shù)值類型到另一個數(shù)值轉(zhuǎn)換的轉(zhuǎn)換,此轉(zhuǎn)換不能用已知的隱式數(shù)值轉(zhuǎn)換實(shí)現(xiàn),它包括:◆從sbyte到byte、ushort、uint、ulong或char。

◆從byte到sbyte

和char?!魪膕hort到sbyte、byte、ushort、uint、ulong或char?!魪膗short到sbyte、byte、short或char?!魪膇nt到sbyte、byte、short、ushort、uint、ulong或char?!魪膗int到sbyte、byte、short、ushort、int或char。◆從long到sbyte、byte、short、ushort、int、uint、ulong或char?!魪膗long到sbyte、byte、short、ushort、int、uint、long或char?!魪腸har到sbyte、byte或short?!魪膄loat到sbyte、byte、short、ushort、int、uint、long、ulong、char或decimal?!魪膁ouble到sbyte、byte、short、ushort、int、uint、long、ulong、char、float或decimal?!魪膁ecimal到sbyte、byte、short、ushort、int、uint、long、ulong、char、float或double。由于顯式轉(zhuǎn)換包括所有隱式和顯式數(shù)值轉(zhuǎn)換,因此總是可以使用強(qiáng)制轉(zhuǎn)換表達(dá)式從任何數(shù)值類型轉(zhuǎn)換為任何其他的數(shù)值類型。顯式數(shù)值轉(zhuǎn)換有可能丟失信息或?qū)е乱l(fā)異常。顯式數(shù)值轉(zhuǎn)換按下面所述處理:◆對于從一個整型到另一個整型的轉(zhuǎn)換,處理取決于該轉(zhuǎn)換發(fā)生時的溢出檢查上下文:1)在checked上下文中,如果源操作數(shù)的值在目標(biāo)類型的范圍內(nèi),轉(zhuǎn)換就會成功,但如果源操作數(shù)的值在目標(biāo)類型的范圍外,則會引發(fā)System.OverflowException。2)在unchecked上下文中,轉(zhuǎn)換總是會成功并按下面所述進(jìn)行。如果源類型大于目標(biāo)類型,則截斷源值(截去源值中容不下的最高有效位)。然后將結(jié)果視為目標(biāo)類型的值。如果源類型小于目標(biāo)類型,則源值或按符號擴(kuò)展或按零擴(kuò)展,以使它的大小與目標(biāo)類型相同。如果源類型是有符號的,則使用按符號擴(kuò)展;如果源類型是無符號的,則使用按零擴(kuò)展。然后將結(jié)果視為目標(biāo)類型的值。如果源類型的大小與目標(biāo)類型相同,則源值被視為目標(biāo)類型的值?!魧τ趶膁ecimal到整型的轉(zhuǎn)換,源值向零舍入到最接近的整數(shù)值,該整數(shù)值成為轉(zhuǎn)換的結(jié)果。如果轉(zhuǎn)換得到的整數(shù)值不在目標(biāo)類型的范圍內(nèi),則會引發(fā)System.OverflowException?!魧τ趶膄loat或double到整型的轉(zhuǎn)換,處理取決于發(fā)生該轉(zhuǎn)換時的溢出檢查上下文:1)在checked上下文中,如下所示進(jìn)行轉(zhuǎn)換:如果操作數(shù)的值是NaN或無窮大,則引發(fā)System.OverflowException。否則,源操作數(shù)會向零舍入到最接近的整數(shù)值。如果該整數(shù)值處于目標(biāo)類型的范圍內(nèi),則該值就是轉(zhuǎn)換的結(jié)果。否則,引發(fā)System.OverflowException。2)在unchecked上下文中,轉(zhuǎn)換總是會成功并按下面這樣繼續(xù)。

如果操作數(shù)的值是NaN或infinite,則轉(zhuǎn)換的結(jié)果是目標(biāo)類型的一個未經(jīng)指定的值。否則,源操作數(shù)會向零舍入到最接近的整數(shù)值。如果該整數(shù)值處于目標(biāo)類型的范圍內(nèi),則該值就是轉(zhuǎn)換的結(jié)果。否則,轉(zhuǎn)換的結(jié)果是目標(biāo)類型的一個未經(jīng)指定的值。◆對于從double到float的轉(zhuǎn)換,double值舍入到最接近的float值。如果double值過小,無法表示為float值,則結(jié)果變成正零或負(fù)零。如果double值過大,無法表示為float值,則結(jié)果變成正無窮大或負(fù)無窮大。如果double值為NaN,則結(jié)果仍然是NaN?!魧τ趶膄loat或double到decimal的轉(zhuǎn)換,源值轉(zhuǎn)換為用decimal形式來表示,并且在需要時,將它在第28位小數(shù)位數(shù)上舍入到最接近的數(shù)字。如果源值過小,無法表示為decimal,則結(jié)果變成零。如果源值為NaN、無窮大或者太大而無法表示為decimal值,則將引發(fā)System.OverflowException?!魧τ趶膁ecimal到float或double的轉(zhuǎn)換,decimal值舍入到最接近的double或float值。雖然這種轉(zhuǎn)換可能會損失精度,但決不會導(dǎo)致引發(fā)異常。

2、顯式枚舉轉(zhuǎn)換為:◆從sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double或decimal到任何枚舉類型。◆從任何枚舉類型到sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double或decimal?!魪娜魏蚊杜e類型到任何其他枚舉類型。兩種類型之間的顯式枚舉轉(zhuǎn)換是通過將任何參與的枚舉類型都按該枚舉類型的基礎(chǔ)類型處理,然后在結(jié)果類型之間執(zhí)行隱式或顯式數(shù)值轉(zhuǎn)換。例如,給定具有int基礎(chǔ)類型的枚舉類型E,從E到byte的轉(zhuǎn)換按從int到byte的顯式數(shù)值轉(zhuǎn)換處理,而從byte到E的轉(zhuǎn)換按從byte到int的隱式數(shù)值轉(zhuǎn)換處理。3、用戶定義的顯式轉(zhuǎn)換由以下三個部分組成:先是一個標(biāo)準(zhǔn)的顯式轉(zhuǎn)換(可選),然后是執(zhí)行用戶定義的隱式或顯式轉(zhuǎn)換運(yùn)算符,最后是另一個標(biāo)準(zhǔn)的顯式轉(zhuǎn)換(可選)。從S類型到T類型的用戶定義的顯式轉(zhuǎn)換按下面這樣處理:1)查找類型集D,將從該類型集考慮用戶定義的轉(zhuǎn)換運(yùn)算符。該類型集由S(如果S為類或結(jié)構(gòu))、S的基類(如果S為類)、T(如果T為類或結(jié)構(gòu))和T的基類(如果T為類)組成。2)查找適用的用戶定義轉(zhuǎn)換運(yùn)算符集合U。集合U由用戶定義的隱式或顯式轉(zhuǎn)換運(yùn)算符組成,這些運(yùn)算符是在D中的類或結(jié)構(gòu)內(nèi)聲明的,用于從包含S或被S包含的類型轉(zhuǎn)換為包含T或被T包含的類型。如果U為空,則轉(zhuǎn)換未定義并且發(fā)生編譯時錯誤。3)在U中查找運(yùn)算符的最精確的源類型SX:①如果U中的所有運(yùn)算符都從S轉(zhuǎn)換,則SX為S。

②否則,如果U中的所有運(yùn)算符都從包含S的類型轉(zhuǎn)換,則SX在這些運(yùn)算符的合并源類型集中是被包含程度最大的類型。如果找不到最直接包含的類型,則轉(zhuǎn)換是不明確的,并且發(fā)生編譯時錯誤。

③否則,SX在U中運(yùn)算符的合并源類型集中是包含程度最大的類型。如果找不到這樣的包含程度最大的類型,則轉(zhuǎn)換是不明確的,并且發(fā)生編譯時錯誤。4)在U中查找運(yùn)算符的最精確的目標(biāo)類型TX:①如果U中的所有運(yùn)算符都轉(zhuǎn)換為T,則TX為T。②否則,如果U中的所有運(yùn)算符都轉(zhuǎn)換為被T包含的類型,則TX在這些運(yùn)算符的合并源類型集中是包含程度最大的類型。如果找不到這樣的包含程度最大的類型,則轉(zhuǎn)換是不明確的,并且發(fā)生編譯時錯誤。③否則,TX在U中運(yùn)算符的合并目標(biāo)類型集中是被包含程度最大的類型。如果找不到這樣的被包含程度最大的類型,則轉(zhuǎn)換是不明確的,并且發(fā)生編譯時錯誤。5)如果U中正好含有一個從SX轉(zhuǎn)換到TX的用戶定義轉(zhuǎn)換運(yùn)算符,則這就是最精確的轉(zhuǎn)換運(yùn)算符。如果不存在此類運(yùn)算符,或者如果存在多個此類運(yùn)算符,則轉(zhuǎn)換是不明確的,并且發(fā)生編譯時錯誤。否則,將應(yīng)用用戶定義的轉(zhuǎn)換:①如果S不是SX,則先執(zhí)行一個從S到SX的標(biāo)準(zhǔn)顯式轉(zhuǎn)換。②調(diào)用最精確的用戶定義轉(zhuǎn)換運(yùn)算符,以從SX轉(zhuǎn)換到TX。③如果TX不是T,則再執(zhí)行一個從TX到T的標(biāo)準(zhǔn)顯式轉(zhuǎn)換。標(biāo)準(zhǔn)顯式轉(zhuǎn)換包括所有的標(biāo)準(zhǔn)隱式轉(zhuǎn)換以及一個顯式轉(zhuǎn)換的子集,該子集是由那些與已知的標(biāo)準(zhǔn)隱式轉(zhuǎn)換反向的轉(zhuǎn)換組成的。換言之,如果存在一個從A類型到B類型的標(biāo)準(zhǔn)隱式轉(zhuǎn)換,則一定存在與其對應(yīng)的兩個標(biāo)準(zhǔn)顯式轉(zhuǎn)換(一個是從A類型到B類型,另一個是從B類型到A類型)。4.3.3類的引用轉(zhuǎn)換1、隱式引用轉(zhuǎn)換為:◆從任何引用類型到object?!魪娜魏晤愵愋蚐到任何類類型T(前提是S是從T派生的)?!魪娜魏晤愵愋蚐到任何接口類型T(前提是S實(shí)現(xiàn)了T)?!魪娜魏谓涌陬愋蚐到任何接口類型T(前提是S是從T派生的)?!魪脑仡愋蜑镾E的數(shù)組類型S到元素類型為TE的數(shù)組類型T(前提是以下所列的條件均為真):1)S和T只是元素類型不同。換言之,S和T具有相同的維數(shù)。

2)SE和TE都是引用類型。

3)存在從SE到TE的隱式引用轉(zhuǎn)換?!魪娜魏螖?shù)組類型到System.Array?!魪娜魏挝蓄愋偷絊ystem.Delegate?!魪膎ull類型到任何引用類型。隱式引用轉(zhuǎn)換是指一類引用類型之間的轉(zhuǎn)換,這種轉(zhuǎn)換總是可以成功,因此不需要在運(yùn)行時進(jìn)行任何檢查。引用轉(zhuǎn)換無論是隱式的還是顯式的,都不會更改所轉(zhuǎn)換的對象的引用標(biāo)識。換言之,雖然引用轉(zhuǎn)換可能改變該引用的類型,但決不會更改所引用對象的類型或值。2、顯式引用轉(zhuǎn)換為:

◆從object到任何其他引用類型?!魪娜魏晤愵愋蚐到任何類類型T(前提是S為T的基類)?!魪娜魏晤愵愋蚐到任何接口類型T(前提是S未密封并且S不實(shí)現(xiàn)T)?!魪娜魏谓涌陬愋蚐到任何類類型T(前提是T未密封或T實(shí)現(xiàn)S)?!魪娜魏谓涌陬愋蚐到任何接口類型T(前提是S不是從T派生的)?!魪脑仡愋蜑镾E的數(shù)組類型S到元素類型為TE的數(shù)組類型T(前提是以下所列條件均為真):1)S和T只是元素類型不同。換言之,S和T具有相同的維數(shù)。2)SE和TE都是引用類型。3)存在從SE到TE的顯式引用轉(zhuǎn)換。◆從System.Array以及它實(shí)現(xiàn)的接口到任何數(shù)組類型。

◆從System.Delegate以及它實(shí)現(xiàn)的接口到任何委托類型。顯式引用轉(zhuǎn)換是那些需要運(yùn)行時檢查以確保它們正確的引用類型之間的轉(zhuǎn)換。為了使顯式引用轉(zhuǎn)換在運(yùn)行時成功,源操作數(shù)的值必須為null,或源操作數(shù)所引用的對象的實(shí)際類型必須是一個可通過隱式引用轉(zhuǎn)換轉(zhuǎn)換為目標(biāo)類型的類型。如果顯式引用轉(zhuǎn)換失敗,則將引發(fā)System.InvalidCastException。引用轉(zhuǎn)換無論是隱式的還是顯式的,都不會更改被轉(zhuǎn)換的對象的引用標(biāo)識。換言之,雖然引用轉(zhuǎn)換可能更改引用的類型,但決不會更改所引用對象的類型或值。4.3.4裝箱與拆箱

裝箱轉(zhuǎn)換允許將值類型隱式轉(zhuǎn)換為引用類型。將值類型的一個值裝箱包括以下操作:分配一個對象實(shí)例,然后將值類型的值復(fù)制到該實(shí)例中。裝箱轉(zhuǎn)換允許將“值類型”隱式轉(zhuǎn)換為“引用類型”。存在下列裝箱轉(zhuǎn)換:◆從任何“值類型”(包括任何“枚舉類型”)到類型object?!魪娜魏巍爸殿愋汀保òㄈ魏巍懊杜e類型”)到類型System.ValueType?!魪娜魏巍爸殿愋汀钡健爸殿愋汀睂?shí)現(xiàn)的任何“接口類型”?!魪娜魏巍懊杜e類型”到System.Enum類型。將“值類型”的值裝箱的操作包括:分配一個對象實(shí)例并將“值類型”的值復(fù)制到該實(shí)例中。最能說明“值類型”的值的實(shí)際裝箱過程的辦法是,設(shè)想有一個為該類型設(shè)置的裝箱類。對任何“值類型”的T而言,裝箱類的行為可用下列聲明來描述:sealedclassT_Box:System.ValueType { Tvalue; publicT_Box(Tt) { value=t; } }T類型值v的裝箱過程現(xiàn)在包括執(zhí)行表達(dá)式newT_Box(v)和將結(jié)果實(shí)例作為object類型的值返回。因此,下面的語句:inti=123;objectbox=i;在概念上相當(dāng)于:inti=123;objectbox=newint_Box(i);實(shí)際上,像上面這樣的T_Box和int_Box并不存在,并且裝了箱的值的動態(tài)類型也不會真的屬于一個類類型。相反,T類型的裝了箱的值屬于動態(tài)類型T,若用is運(yùn)算符來檢查動態(tài)類型的話,也僅能引用類型T。例如,inti=123;

objectbox=i;

if(boxisint)

{Console.Write("Boxcontainsanint");

}將在控制臺上輸出字符串“Boxcontainsanint”。裝箱轉(zhuǎn)換隱含著復(fù)制一份欲被裝箱的值。這不同于從引用類型到object類型的轉(zhuǎn)換,在后一種轉(zhuǎn)換中,轉(zhuǎn)換后的值繼續(xù)引用同一實(shí)例,只是將它當(dāng)作派生程度較小的object類型而已。例如,設(shè)有下列的聲明:structPoint{publicintx,y;publicPoint(intx,inty){this.x=x;

this.y=y;}}則下面的語句:

Pointp=newPoint(10,10);

objectbox=p;

p.x=20;

Console.Write(((Point)box).x);將在控制臺上輸出值10,因?yàn)閷賦值給box是一個隱式裝箱操作,它將復(fù)制p的值。如果將Point聲明為class,由于p和box將會引用同一個實(shí)例,因此輸出值為20。拆箱也稱為取消裝箱轉(zhuǎn)換。取消裝箱轉(zhuǎn)換允許將引用類型顯式轉(zhuǎn)換為值類型。一個取消裝箱操作包括以下兩個步驟:首先檢查對象實(shí)例是否為給定值類型的一個裝了箱的值,然后將該值從實(shí)例中復(fù)制出來。取消裝箱轉(zhuǎn)換允許將引用類型顯式轉(zhuǎn)換為值類型。存在以下取消裝箱轉(zhuǎn)換:◆從類型object到任何值類型(包括任何枚舉類型)。◆從類型System.ValueType

到任何值類型(包括任何枚舉類型)。◆從任何接口類型到實(shí)現(xiàn)了該接口類型的任何值類型。◆從System.Enum

類型到任何枚舉類型。取消裝箱操作包括以下兩個步驟:首先檢查該對象實(shí)例是否是某個給定的值類型的裝了箱的值,然后將值從實(shí)例中復(fù)制出來。參照前面關(guān)于裝箱類的描述,從對象box到值類型T的取消裝箱轉(zhuǎn)換相當(dāng)于執(zhí)行表達(dá)式((T_Box)box).value。因此,下面的語句:objectbox=123;inti=(int)box;在概念上相當(dāng)于:objectbox=newint_Box(123);inti=((int_Box)box).value;為使到給定值類型的取消裝箱轉(zhuǎn)換在運(yùn)行時取得成功,源操作數(shù)的值必須是對某個對象的引用,而該對象先前是通過將該值類型的某個值裝箱而創(chuàng)建的。如果源操作數(shù)為null,則將引發(fā)System.NullReferenceException。如果源操作數(shù)是對不兼容對象的引用,則將引發(fā)System.InvalidCastException。4.4結(jié)構(gòu)和接口

4.4.1結(jié)構(gòu)結(jié)構(gòu)與類有很多相似之處:結(jié)構(gòu)可以實(shí)現(xiàn)接口,并且可以具有與類相同的成員類型。然而,結(jié)構(gòu)在幾個重要方面不同于類:結(jié)構(gòu)為值類型而不是引用類型,并且結(jié)構(gòu)不支持繼承。結(jié)構(gòu)的值存儲在“在堆棧上”或“內(nèi)聯(lián)”。細(xì)心的程序員有時可以通過聰明地使用結(jié)構(gòu)來增強(qiáng)性能。聲明一個結(jié)構(gòu),它有三個成員:一個屬性、一個方法和一個私有字段,創(chuàng)建該結(jié)構(gòu)的一個實(shí)例,并將其投入使用:案例:聲明一個結(jié)構(gòu)目標(biāo):了解聲明結(jié)構(gòu)的基本方法步驟:1、啟動VS.NET,新建一個控制臺應(yīng)用程序,名稱填寫為“SimpleStructTest1”,位置設(shè)置為“c:\CSharpSamples\chp4”。2、在代碼設(shè)計窗口中編輯Class1.cs。其中的代碼編寫如下:usingSystem;namespaceSimpleStructTest1{

struct

SimpleStruct { privateint

xval; publicintX { get { returnxval; } set {if(value<100)

xval=value;} } publicvoidDisplayX() {Console.WriteLine("Thestoredvalueis:{0}",xval); } } classTestClass {publicstaticvoidMain() {SimpleStruct

ss=newSimpleStruct();

ss.X=5;

ss.DisplayX(); } }}3、按Ctrl+F5編譯并運(yùn)行該程序,效果如圖4-4所示。

將Point定義為結(jié)構(gòu)而不是類在運(yùn)行時可以節(jié)省很多內(nèi)存空間。下面的程序創(chuàng)建并初始化一個100點(diǎn)的數(shù)組。對于作為類實(shí)現(xiàn)的Point,出現(xiàn)了101個實(shí)例對象,因?yàn)閿?shù)組需要一個,它的100個元素每個都需要一個。圖4-4程序運(yùn)行結(jié)果程序清單:usingSystem;namespaceConsoleApplication1{classPoint {publicintx,y; publicPoint(intx,inty) {this.x=x;

this.y=y; } } classTest {staticvoidMain() {Point[]points=newPoint[100]; for(inti=0;i<100;i++)

points[i]=newPoint(i,i*i);} }}如果將Point改為作為結(jié)構(gòu)實(shí)現(xiàn),如:structPoint { publicintx,y; publicPoint(intx,inty) {

this.x=x;

this.y=y; } }則只出現(xiàn)一個實(shí)例對象(用于數(shù)組的對象)。Point實(shí)例在數(shù)組中內(nèi)聯(lián)分配。此優(yōu)化可能會被誤用。使用結(jié)構(gòu)而不是類還會使應(yīng)用程序運(yùn)行得更慢或占用更多的內(nèi)存,因?yàn)閷⒔Y(jié)構(gòu)實(shí)例作為值參數(shù)傳遞會導(dǎo)致創(chuàng)建結(jié)構(gòu)的副本。案例:當(dāng)向方法傳遞結(jié)構(gòu)時,將傳遞該結(jié)構(gòu)的副本,而傳遞類實(shí)例時,將傳遞一個引用。目標(biāo):掌握傳遞結(jié)構(gòu)的方法步驟:1、啟動VS.NET,新建一個控制臺應(yīng)用程序,名稱填寫為“ClasstakerTest”,位置設(shè)置為“c:\CSharpSamples\chp4”。2、在代碼設(shè)計窗口中編輯Class1.cs。其中的代碼編寫如下:usingSystem;

namespaceClasstakerTest

{classTheClass {publicintx; }

struct

TheStruct {publicintx; } classTestClass {publicstaticvoidstructtaker(TheStructs) {

s.x=5; }

publicstaticvoidclasstaker(TheClassc)

{c.x=5;} publicstaticvoidMain() {TheStructa=newTheStruct();

TheClassb=newTheClass();

a.x=1;

b.x=1;

structtaker(a);

classtaker(b);

Console.WriteLine("a.x={0}",a.x);

Console.WriteLine("b.x={0}",b.x); } }}3、按Ctrl+F5編譯并運(yùn)行該程序本示例的輸出表明:當(dāng)向classtaker方法傳遞類實(shí)例時,只更改了類字段的值。但是向structtaker方法傳遞結(jié)構(gòu)實(shí)例并不更改結(jié)構(gòu)字段。這是因?yàn)橄騭tructtaker方法傳遞的是結(jié)構(gòu)的副本,而向classtaker方法傳遞的是對類的引用。結(jié)構(gòu)可以聲明構(gòu)造函數(shù),但它們必須帶參數(shù)。聲明結(jié)構(gòu)的默認(rèn)(無參數(shù))構(gòu)造函數(shù)是錯誤的。結(jié)構(gòu)成員不能有初始值設(shè)定項??偸翘峁┠J(rèn)構(gòu)造函數(shù)以將結(jié)構(gòu)成員初始化為它們的默認(rèn)值。使用New運(yùn)算符創(chuàng)建結(jié)構(gòu)對象時,將創(chuàng)建該結(jié)構(gòu)對象,并且調(diào)用適當(dāng)?shù)臉?gòu)造函數(shù)。與類不同的是,結(jié)構(gòu)的實(shí)例化可以不使用New運(yùn)算符。如果不使用“新建”(new),那么在初始化所有字段之前,字段將保持未賦值狀態(tài),且對象不可用。對于結(jié)構(gòu),不像類那樣存在繼承。一個結(jié)構(gòu)不能從另一個結(jié)構(gòu)或類繼承,而且不能作為一個類的基。但是,結(jié)構(gòu)從基類對象繼承。結(jié)構(gòu)可實(shí)現(xiàn)接口,而且實(shí)現(xiàn)方式與類實(shí)現(xiàn)接口的方式完全相同。以下是結(jié)構(gòu)實(shí)現(xiàn)接口的代碼片段:interfaceIImage

{

voidPaint();

}

structPicture:IImage

{

publicvoidPaint()

{

//paintingcodegoeshere

}

privateintx,y,z;//otherstructmembers

}結(jié)構(gòu)使用簡單,并且有時證明很有用。但要牢記:結(jié)構(gòu)在堆棧中創(chuàng)建,并且您不是處理對結(jié)構(gòu)的引用,而是直接處理結(jié)構(gòu)。每當(dāng)需要一種將經(jīng)常使用的類型,而且大多數(shù)情況下該類型只是一些數(shù)據(jù)時,結(jié)構(gòu)可能是最佳選擇。4.4.2接口

什么是接口

接口(interface)用來定義一種程序的協(xié)定。實(shí)現(xiàn)接口的類或者結(jié)構(gòu)要與接口的定義嚴(yán)格一致。有了這個協(xié)定,就可以拋開編程語言的限制(理論上)。接口可以從多個基接口繼承,而類或結(jié)構(gòu)可以實(shí)現(xiàn)多個接口。接口可以包含方法、屬性、事件和索引器。接口本身不提供它所定義的成員的實(shí)現(xiàn)。接口只指定實(shí)現(xiàn)該接口的類或接口必須提供的成員。接口好比一種模版,這種模版定義了對象必須實(shí)現(xiàn)的方法,其目的就是讓這些方法可以作為接口實(shí)例被引用。接口不能被實(shí)例化。類可以實(shí)現(xiàn)多個接口并且通過這些實(shí)現(xiàn)的接口被索引。接口變量只能索引實(shí)現(xiàn)該接口的類的實(shí)例。例子:interfaceImyExample{stringthis[intindex]{get;set;}eventEventHandlerEven;voidFind(intvalue);stringPoint{get;set;}}publicdelegatevoidEventHandler(objectsender,Evente);上面例子中的接口包含一個索引this、一個事件Even、一個方法Find和一個屬性Point。

接口可以支持多重繼承。就像在下例中,接口“IcomboBox”同時從“ItextBox”和“IlistBox”繼承。interfaceIControl

{voidPaint();}interfaceITextBox:IControl

{voidSetText(stringtext);}interfaceIListBox:IControl

{voidSetItems(string[]items);}interfaceIComboBox:ITextBox,IListBox{}類和結(jié)構(gòu)可以多重實(shí)例化接口。就像在下例中,類“EditBox”繼承了類“Control”,同時從“IdataBound”和“Icontrol”繼承。interfaceIDataBound

{voidBind(Binderb);}publicclassEditBox:Control,IControl,IDataBound

{publicvoidPaint();publicvoidBind(Binderb){...}}在上面的代碼中,“Paint”方法從“Icontrol”接口而來;“Bind”方法從“IdataBound”接口而來,都以“public”的身份在“EditBox”類中實(shí)現(xiàn)。說明:1、C#中的接口是獨(dú)立于類來定義的。這與C++模型是對立的,在C++中接口實(shí)際上就是抽象基類。2、接口和類都可以繼承多個接口。3、而類可以繼承一個基類,接口根本不能繼承類。這種模型避免了C++的多繼承問題,C++中不同基類中的實(shí)現(xiàn)可能出現(xiàn)沖突。因此也不再需要諸如虛擬繼承和顯式作用域這類復(fù)雜機(jī)制。C#的簡化接口模型有助于加快應(yīng)用程序的開發(fā)。4、一個接口定義一個只有抽象成員的引用類型。C#中一個接口實(shí)際所做的,僅僅只存在著方法標(biāo)志,但根本就沒有執(zhí)行代碼。這就暗示了不能實(shí)例化一個接口,只能實(shí)例化一個派生自該接口的對象。5、接口可以定義方法、屬性和索引。所以,對比一個類,接口的特殊性是:當(dāng)定義一個類時,可以派生自多重接口,而你只能可以從僅有的一個類派生?!艚涌谂c組件接口描述了組件對外提供的服務(wù)。在組件和組件之間、組件和客戶之間都通過接口進(jìn)行交互。因此組件一旦發(fā)布,它只能通過預(yù)先定義的接口來提供合理的、一致的服務(wù)。這種接口定義之間的穩(wěn)定性使客戶應(yīng)用開發(fā)者能夠構(gòu)造出堅固的應(yīng)用。一個組件可以實(shí)現(xiàn)多個組件接口,而一個特定的組件接口也可以被多個組件來實(shí)現(xiàn)。組件接口必須是能夠自我描述的。這意味著組件接口應(yīng)該不依賴于具體的實(shí)現(xiàn),將實(shí)現(xiàn)和接口分離徹底消除了接口的使用者和接口的實(shí)現(xiàn)者之間的耦合關(guān)系,增強(qiáng)了信息的封裝程度。同時這也要求組件接口必須使用一種與組件實(shí)現(xiàn)無關(guān)的語言。目前組件接口的描述標(biāo)準(zhǔn)是IDL語言。由于接口是組件之間的協(xié)議,因此組件的接口一旦被發(fā)布,組件生產(chǎn)者就應(yīng)該盡可能地保持接口不變,任何對接口語法或語義上的改變,都有可能造成現(xiàn)有組件與客戶之間的聯(lián)系遭到破壞。

每個組件都是自主的,有其獨(dú)特的功能,只能通過接口與外界通信。當(dāng)一個組件需要提供新的服務(wù)時,可以通過增加新的接口來實(shí)現(xiàn)。不會影響原接口已存在的客戶。而新的客戶可以重新選擇新的接口來獲得服務(wù)?!艚M件化程序設(shè)計組件化程序設(shè)計方法繼承并發(fā)展了面向?qū)ο蟮某绦蛟O(shè)計方法。它把對象技術(shù)應(yīng)用于系統(tǒng)設(shè)計,對面向?qū)ο蟮某绦蛟O(shè)計的實(shí)現(xiàn)過程作了進(jìn)一步的抽象。我們可以把組件化程序設(shè)計方法用作構(gòu)造系統(tǒng)的體系結(jié)構(gòu)層次的方法,并且可以使用面向?qū)ο蟮姆椒ê芊奖愕貙?shí)現(xiàn)組件。組件化程序設(shè)計強(qiáng)調(diào)真正的軟件可重用性和高度的互操作性。它側(cè)重于組件的產(chǎn)生和裝配,這兩方面一起構(gòu)成了組件化程序設(shè)計的核心。組件的產(chǎn)生過程不僅僅是應(yīng)用系統(tǒng)的需求,組件市場本身也推動了組件的發(fā)展,促進(jìn)了軟件廠商的交流與合作。組件的裝配使得軟件產(chǎn)品可以采用類似于搭積木的方法快速地建立起來,不僅可以縮短軟件產(chǎn)品的開發(fā)周期,同時也提高了系統(tǒng)的穩(wěn)定性和可靠性。組件程序設(shè)計的方法有以下幾個方面的特點(diǎn):1、編程語言和開發(fā)環(huán)境的獨(dú)立性;2、組件位置的透明性;3、組件的進(jìn)程透明性;4、可擴(kuò)充性;5、可重用性;6、具有強(qiáng)有力的基礎(chǔ)設(shè)施;7、系統(tǒng)一級的公共服務(wù)。C#語言由于其許多優(yōu)點(diǎn),十分適用于組件編程。但這并不是說C#是一門組件編程語言,也不是說C#提供了組件編程的工具。我們已經(jīng)多次指出,組件應(yīng)該具有與編程語言無關(guān)的特性。請讀者記住這一點(diǎn):組件模型是一種規(guī)范,不管采用何種程序語言設(shè)計組件,都必須遵守這一規(guī)范。比如組裝計算機(jī)的例子,只要各個廠商為我們提供的配件規(guī)格、接口符合統(tǒng)一的標(biāo)準(zhǔn),這些配件組合起來就能協(xié)同工作,組件編程也是一樣。我們只是說,利用C#語言進(jìn)行組件編程將會給我們帶來更大的方便。

知道了什么是接口,接下來就是怎樣定義接口。

定義接口從技術(shù)上講,接口是一組包含了函數(shù)型方法的數(shù)據(jù)結(jié)構(gòu)。通過這組數(shù)據(jù)結(jié)構(gòu),客戶代碼可以調(diào)用組件對象的功能。定義接口的一般形式為:

[接口修飾符]interface接口名[:基類接口名]

{

//接口的成員;

}其中接口的修飾符可以是new、public、protected、internal和private。New修飾符是在嵌套接口中唯一允許存在的修飾符,它說明用相同的名稱隱藏一個繼承的成員。Public、proteced、internal和pricate修飾符控制接口的訪問能力。接口這個概念在C#和Java中非常相似。接口的關(guān)鍵詞是interface,一個接口可以擴(kuò)展一個或者多個其他接口。按照慣例,接口的名字以大寫字母“I”開頭。下面的代碼是C#接口的一個例子,它與Java中的接口完全一樣:interfaceIShape

{

voidDraw

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論