熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> .NET編程 >> 正文

C#銳利體驗之第六講 方法

2013-11-13 10:25:23  來源: .NET編程 

  方法又稱成員函數(Member Function)集中體現了類或對象的行為方法同樣分為靜態方法和實例方法靜態方法只可以操作靜態域而實例方法既可以操作實例域也可以操作靜態域雖然這不被推薦但在某些特殊的情況下會顯得很有用方法也有如域一樣的種存取修飾符publicprotectedinternalprotected internalprivate它們的意義如前所述

  方法參數

  方法的參數是個值得特別注意的地方方法的參數傳遞有四種類型傳值(by value)傳址(by reference)輸出參數(by output)數組參數(by array)傳值參數無需額外的修飾符傳址參數需要修飾符ref輸出參數需要修飾符out數組參數需要修飾符params傳值參數在方法調用過程中如果改變了參數的值那麼傳入方法的參數在方法調用完成以後並不因此而改變而是保留原來傳入時的值傳址參數恰恰相反如果方法調用過程改變了參數的值那麼傳入方法的參數在調用完成以後也隨之改變實際上從名稱上我們可以清楚地看出兩者的含義傳值參數傳遞的是調用參數的一份拷貝而傳址參數傳遞的是調用參數的內存地址該參數在方法內外指向的是同一個存儲位置看下面的例子及其輸出

  using System;class Test{static void Swap(ref int x ref int y) {int temp = x;x = y;y = temp;}static void Swap(int xint y) {int temp = x;x = y;y = temp;}static void Main() {int i = j = ;Swap(ref i ref j);ConsoleWriteLine(i = {} j = {} i j);Swap(ij);ConsoleWriteLine(i = {} j = {} i j);}}
程序經編譯後執行輸出

  i = j =
i = j =
我們可以清楚地看到兩個交換函數Swap()由於參數的差別傳值與傳址而得到不同的調用結果注意傳址參數的方法調用無論在聲明時還是調用時都要加上ref修飾符

  籠統地說傳值不會改變參數的值在有些情況下是錯誤的我們看下面一個例子

  using System;class Element{public int Number=;}class Test{static void Change(Element s){sNumber=;}static void Main() {Element e=new Element();ConsoleWriteLine(eNumber);Change(e);ConsoleWriteLine(eNumber);}}
程序經編譯後執行輸出

  

我們看到即使傳值方式仍然改變了類型為Element類的對象t但嚴格意義上講我們是改變了對象t的域而非對象t本身我們再看下面的例子

  using System;class Element{public int Number=;}class Test{static void Change(Element s){Element r=new Element();rNumber=;s=r;}static void Main() {Element e=new Element();ConsoleWriteLine(eNumber);Change(e);ConsoleWriteLine(eNumber);}}
程序經編譯後執行輸出

  

傳值方式根本沒有改變類型為Element類的對象t!實際上如果我們能夠理解類這一C#中的引用類型(reference type)的特性我們便能看出上面兩個例子差別!在傳值過程中引用類型本身不會改變(t不會改變)但引用類型內含的域卻會改變(tNumber改變了)!C#語言的引用類型有object類型(包括系統內建的class類型和用戶自建的class類型繼承自object類型)string類型interface類型array類型delegate類型它們在傳值調用中都有上面兩個例子展示的特性

  在傳值和傳址情況下C#強制要求參數在傳入之前由用戶明確初始化否則編譯器報錯!但我們如果有一個並不依賴於參數初值的函數我們只是需要函數返回時得到它的值是該怎麼辦呢?往往在我們的函數返回值不至一個時我們特別需要這種技巧答案是用out修飾的輸出參數但需要記住輸出參數與通常的函數返回值有一定的區別函數返回值往往存在堆棧裡在返回時彈出而輸出參數需要用戶預先制定存儲位置也就是用戶需要提前聲明變量當然也可以初始化看下面的例子

  using System;class Test{static void ResoluteName(string fullnameout string firstnameout string lastname) {string[] strArray=fullnameSplit(new char[]{ });firstname=strArray[];lastname=strArray[];}public static void Main() {string MyName=Cornfield Lee;string MyFirstNameMyLastName;ResoluteName(MyNameout MyFirstNameout MyLastName);ConsoleWriteLine(My first name: {} My last name: {} MyFirstName MyLastName);}}
程序經編譯後執行輸出

  My first name: Cornfield My last name: Lee
在函數體內所有輸出參數必須被賦值否則編譯器報錯!out修飾符同樣應該應用在函數聲明和調用兩個地方除了充當返回值這一特殊的功能外out修飾符ref修飾符有很相似的地方傳址我們可以看出C#完全擯棄了傳統C/C++語言賦予程序員莫大的自由度畢竟C#是用來開發高效的下一代網絡平台安全性包括系統安全(系統結構的設計)和工程安全(避免程序員經常犯的錯誤)是它設計時的重要考慮當然我們看到C#並沒有因為安全性而喪失多少語言的性能這正是C#的卓越之處Sharp之處!

  數組參數也是我們經常用到的一個地方傳遞大量的數組集合參數我們先看下面的例子

  using System;class Test{static int Sum(params int[] args){int s=;foreach(int n in args){s+=n;}return s;}static void Main() {int[] var=new int[]{};ConsoleWriteLine(The Sum:+Sum(var));ConsoleWriteLine(The Sum:+Sum());}}
程序經編譯後執行輸出

  The Sum:
The Sum:
可以看出數組參數可以是數組如var也可以是能夠隱式轉化為數組的參數如這為我們的程序提供了很高的擴展性

  同名方法參數的不同會導致方法出現多態現象這又叫重載(overloading)方法需要指出的是編譯器是在編譯時便綁定了方法和方法調用只能通過參數的不同來重載方法其他的不同(如返回值)不能為編譯器提供有效的重載信息

  方法繼承
第一等的面向對象機制為C#的方法引入了virtualoverridesealedabstract四種修飾符來提供不同的繼承需求類的虛方法是可以在該類的繼承自類中改變其實現的方法當然這種改變僅限於方法體的改變而非方法頭(方法聲明)的改變被子類改變的虛方法必須在方法頭加上override來表示當一個虛方法被調用時該類的實例亦即對象的運行時類型(runtime type)來決定哪個方法體被調用我們看下面的例子

  using System;class Parent{public void F() { ConsoleWriteLine(ParentF); }public virtual void G() { ConsoleWriteLine(ParentG); }}class Child: Parent{new public void F() { ConsoleWriteLine(ChildF); }public override void G() { ConsoleWriteLine(ChildG); }}class Test{static void Main() {Child b = new Child();Parent a = b;aF();bF();aG();bG();}}
程序經編譯後執行輸出

  ParentF
ChildF
ChildG
ChildG
我們可以看到class Child中F()方法的聲明采取了重寫(new)的辦法來屏蔽class Parent中的非虛方法F()的聲明而G()方法就采用了覆蓋(override)的辦法來提供方法的多態機制需要注意的是重寫(new)方法和覆蓋(override)方法的不同從本質上講重寫方法是編譯時綁定而覆蓋方法是運行時綁定值得指出的是虛方法不可以是靜態方法也就是說不可以用static和virtual同時修飾一個方法這由它的運行時類型辨析機制所決定override必須和virtual配合使用當然也不能和static同時使用

  那麼我們如果在一個類的繼承體系中不想再使一個虛方法被覆蓋我們該怎樣做呢?答案是sealed override (密封覆蓋)我們將sealed和override同時修飾一個虛方法便可以達到這種目的sealed override public void F()注意這裡一定是sealed和override同時使用也一定是密封覆蓋一個虛方法或者一個被覆蓋(而不是密封覆蓋)了的虛方法密封一個非虛方法是沒有意義的也是錯誤的看下面的例子

  //sealedcs// csc /t:library sealedcsusing System;class Parent{public virtual void F() {ConsoleWriteLine(ParentF);}public virtual void G() {ConsoleWriteLine(ParentG);}}class Child: Parent{sealed override public void F() {ConsoleWriteLine(ChildF);} override public void G() {ConsoleWriteLine(ChildG);} }class Grandson: Child{override public void G() {ConsoleWriteLine(GrandsonG);} }
抽象(abstract)方法在邏輯上類似於虛方法只是不能像虛方法那樣被調用而只是一個接口的聲明而非實現抽象方法沒有類似於{…}這樣的方法實現也不允許這樣做抽象方法同樣不能是靜態的含有抽象方法的類一定是抽象類也一定要加abstract類修飾符但抽象類並不一定要含有抽象方法繼承含有抽象方法的抽象類的子類必須覆蓋並實現(直接使用override)該方法或者組合使用abstract override使之繼續抽象或者不提供任何覆蓋和實現後兩者的行為是一樣的看下面的例子

  //abstractcs// csc /t:library abstractcsusing System;abstract class Parent{public abstract void F();public abstract void G();}abstract class Child: Parent{public abstract override void F();}abstract class Grandson: Child{public override void F(){ConsoleWriteLine(GrandsonF);}public override void G(){ConsoleWriteLine(GrandsonG);}}
抽象方法可以抽象一個繼承來的虛方法我們看下面的例子

  //abstractcs// csc /t:library abstractcsusing System;class Parent{public virtual void Method(){ConsoleWriteLine(ParentMethod);}}abstract class Child: Parent{public abstract override void Method();}abstract class Grandson: Child{public override void Method(){ConsoleWriteLine(GrandsonMethod);}}
歸根結底我們抓住了運行時綁定和編譯時綁定的基本機理我們便能看透方法呈現出的種種overloadvirtualoverridesealedabstract等形態我們才能運用好方法這一利器!

  外部方法

  C#引入了extern修飾符來表示外部方法外部方法是用C#以外的語言實現的方法如Win API函數如前所是外部方法不能是抽象方法我們看下面的一個例子

  using System;using SystemRuntimeInteropServices;class MyClass{[DllImport(userdll)]static extern int MessageBoxA(int hWnd string msgstring caption int type);public static void Main() {MessageBoxA( Hello World! This is called from a C# app! );}}
程序經編譯後執行輸出

  .NET編程免費提供,內容來源於互聯網,本文歸原作者所有。

推薦文章
Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.