C#快速入門教程(6)——方法

曹化宇發表於2018-09-11

方法(method)是程式設計中重要的一個元素,也是一系列任務執行的關鍵所在,本課,將討論在C#中如何定義方法,包括方法的返回值和不同引數型別的應用;此外,還將討論一些方法的應用特點,主要包括:

  • 引數與返回值
  • 過載方法
  • 建構函式與方法鏈
  • 解構函式

首先看一下方法定義的一般形式:

[<訪問修飾符>] <返回型別> <方法名> ([<引數列表>])
{
    // 方法體
}

這裡:

  • <訪問修飾符>可以定義方法的訪問級別,包含常用的private、public、protected、internal,以及定義靜態方法的static關鍵字、抽象方法的abstract關鍵字、虛擬方法的virtual關鍵字和重寫方法的override關鍵字。如果沒有訪問修飾符,則預設為私有的訪問級別。

  • <返回值型別>是指方法執行後返回的資料型別,如前面使用過的int、string等型別,也可以是後續課程所介紹的各種型別,方法體中可以使用return語句返回執行結果資料。如果方法不需要返回資料,則使用void關鍵字設定。

  • <方法名>當然是指方法的名稱,如前面使用過的Drive()、Return()、ShowLocalModel()方法等。

  • <引數列表>用於帶入方法所需要的資料,如果沒有就空著。引數可以有多個,每個引數最少需要定義資料型別和引數名稱,多個引數使用英文半形逗號分隔;稍後,我們會詳細討論引數的應用問題。

  • 方法體是方法真正實現功能的地方,使用一對花括號定義,這也是語句塊的定義方式。

在後續的學習中會逐步瞭解各種軟體功能的實現,而接下來,我們先討論幾種引數的設定和應用。

引數與返回值

普通引數

普通引數只需要指定資料型別和引數變數,如下面的程式碼,我們在ConsoleTest專案中建立一個名為C5的類,其中定義了一個Add()靜態方法。

namespace ConsoleTest
{
    public class C5
    {
        public static int Add(int x, int y)
        {
            return x + y;
        }
    }
}

方法功能很簡單,只是用來計算兩個32位整數(int型別)的和(很明顯是把事情搞複雜了!^_^);下面的程式碼,我們在Program.cs檔案中測試C5.Add()方法。

using System;

namespace ConsoleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            int x = 10;
            int y = 99;
            Console.WriteLine("{0} + {1} = {2}", x, y, C5.Add(x, y));
        }
    }
}

程式碼執行結果如下圖所示。

enter image description here

現在,注意區分Add()方法中的x和y引數,以及Main()方法中的x和y變數,在前面的課程中,我們提到過資料傳遞過程中的訪問級別問題。在Add()的引數中定義的x和y只能在Add()方法體中使用;Main()方法中定義的x和y變數,在呼叫Add()方法時,會將變數的資料傳遞到Add()方法中,然後將計算結果通過return語句返回,整個過程中並沒有產生歧義,所以,程式碼可以正常執行。

引數預設值

使用方法時,有此引數可能會有一些常用值,此時,可以將引數的資料設定預設值,以方便方法的呼叫。需要注意的是,有預設值的引數應放在所有沒有預設值的引數的後面,如下面的程式碼,我們在C5類中新增了一個名為Add1()的引數。

public static int Add1(int x, int y = 0)
{
    return x + y;
}

引數中,y設定了預設值0,也就是說,在呼叫Add1()方法時,如果不指定引數y的資料,那麼,它的資料就是0,下面,在Program.cs檔案中測試此方法的使用。

using System;

namespace ConsoleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(C5.Add1(10));
            Console.WriteLine(C5.Add1(10, 99));
        }
    }
}

程式碼執行結果如下圖。

enter image description here

引數陣列

前面建立的Add()和Add1()方法,可以使用一個或兩個引數,如果是需要不定數量的引數應該怎麼辦呢?此時,可以使用引數陣列,如下面的程式碼,我們在C5類中定義一個Add2()方法。

public static int Add2(int x, params int[] nums)
{
    int sum = x;
    for (int i = 0; i < nums.Length; i++)
           sum = sum + nums[i];
    return sum;
}

本例中,請注意第二個引數,其定義如下:

params int[] nums

這裡使用params關鍵字定義了一個int型別的陣列引數nums。陣列是指一組相同型別資料的集合,定義時在型別名稱後加一對方括號,如本例中的int[]。

引數陣列在應用時有什麼特點呢?它可以使用零或多個引數,如本例中的Add2()方法,除了第一個引數是必須的,我們還可以使用第二個或更多的引數,如下面的程式碼,我們在Program.cs檔案測試Add2()方法的使用。

using System;

namespace ConsoleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(C5.Add2(10));
            Console.WriteLine(C5.Add2(10, 99));
            Console.WriteLine(C5.Add2(1,2,3,4,5));
        }
    }
}

程式碼執行結果如下圖所示 。

enter image description here

引數陣列應用在引數列表的最後,不應和引數預設值一起使用,那樣會產生歧義。

ref引數

說起ref關鍵字的應用,大家首先還需要理解值型別和引用型別資料的傳遞問題。

C#中的資料型別分為值型別和引用型別,它們在記憶體中的處理方式是不同的,但在直接應用時,表面上可能不太容易看出它們的區別,所在,這裡著重討論按值傳遞和按引用傳遞區別。

在C#中,一些基本的資料型別定義為值型別,如int、long、float、double、decimal等等;值型別的預設傳遞方式是複製其資料,如下面的程式碼。

using System;

namespace ConsoleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            int x = 10;
            int y = x;
            Console.WriteLine("x={0},y={1}",x, y);
            x = 99;
            Console.WriteLine("x={0},y={1}", x, y);
        }
    }
}

先看一下執行結果,如下圖所示。

enter image description here

程式碼中,首先將x賦值為10,然後將x的值賦值給y,此時,由於int是值型別,所以是將x的值複製到y中,y和x並沒有什麼關聯;所以,當修改x的值為99後,y的值依然是10,如上圖所顯示的結果一樣。

int是值型別,而各種class型別則是引用型別,下面的程式碼,我們看一下CAuto型別的賦值操作。

static void Main(string[] args)
{
        CAuto racer = new CAuto()
        {
            DoorCount = 2
        };
        CAuto suv = racer;
        Console.WriteLine("{0},{1}", racer.DoorCount, suv.DoorCount);
        suv.DoorCount = 5;
        Console.WriteLine("{0},{1}", racer.DoorCount, suv.DoorCount);
}

程式碼執行結果如下圖所示。

enter image description here

本例中首先建立了racer物件,然後將其直接賦值給suv物件,此時,實際上是傳遞了racer物件在記憶體中的地址,也就是說suv和racer指向的是同一資料區域;接下來,只修改suv物件的資料,但racer物件的資料也改變了,這一現象證實了,只是通過簡單的賦值,suv和racer物件的確是指向同一資料區域。

使用方法時,值型別的處理方式是複製資料;如果需要在方法中真正地修改原變數的資料,就需要使用ref關鍵字,如下面的程式碼,我們在C5類中定義一個Swap()方法。

public static void Swap(ref int x, ref int y)
{
    int z = x;
   x = y;
   y = z;
}

我們可以看到,Swap()方法的功能是交換兩個int型別變數的值,只是引數定義進使用了ref關鍵字,下面的程式碼,我們在Program.cs檔案中測試這個方法。

static void Main(string[] args)
{
    int x = 10;
    int y = 99;
    Console.WriteLine("x={0},y={1}",x, y);
    C5.Swap(ref x, ref y);
    Console.WriteLine("x={0},y={1}", x, y);
}

程式碼執行結果如下圖所示。

enter image description here

請注意,當引數使用ref關鍵字時,呼叫方法設定引數時也需要使用ref關鍵字。

動手測試:大家可測試一下Swap()方法的引數沒有使用ref關鍵字時的執行結果。

out引數與返回值

我們知道,方法可以通過return語句返回執行結果,但這只是在方法只有一個返回資料的情況下?那麼,方法還能同時返回多個執行結果嗎?

下面,我們先看一個簡單的例子——如何將字串(string)內容轉換為int型別。

static void Main(string[] args)
{
    string s = "123";
    int num = 0;
    bool result = int.TryParse(s, out num);
    Console.WriteLine("轉換結果:{0}", result);
    Console.WriteLine("轉換資料:{0}", num);
}

程式碼中,可以修改s的內容來觀察執行結果。變數num用於存放轉換後的資料,如果不能正確轉換,則初始值是0;變數result用於存放轉換結果,成功轉換為True值(true值),失敗為False值(false值)。轉換操作使用了int.TryParse()靜態方法,其中,第一個引數是需要轉換的字串內容,引數二定義為out引數,用於儲存轉換後的資料;方法的返回值表示轉換操作是否成功。

程式碼執行結果如下圖所示。

enter image description here

如果將s的內容修改為abc,則執行結果如下圖所示。

enter image description here

實際上,我們定義的方法也可以使用out引數,其功能就是讓方法返回更多的資料,如下面的程式碼,我們在C5類中定義一下TryToInt()方法。

public static bool TryToInt(object o, out int result)
{
    if (o == null)
    {
        result = 0;
        return false;
    }
    else
    {
        return int.TryParse(o.ToString(), out result);
    }
}

這裡,我們將資料轉換方法進行了擴充套件,可以嘗試將任何型別的資料轉換為int型別,下面的程式碼,我們在Program.cs檔案中測試此方法的使用。

static void Main(string[] args)
 {
    object obj = "abc";
     int num = 0;
    bool result = C5.TryToInt(obj, out num);
    Console.WriteLine("轉換結果:{0}", result);
    Console.WriteLine("轉換資料:{0}", num);
 }

大家可以修改obj物件的內容來觀察執行結果,可以嘗試空物件的null值、各種數值等型別的資料。

過載方法

現在,回顧一下Console.WriteLine()方法的呼叫,我們使用了多少種引數的組合?它實際上就是有多個版本的過載方法。

過載方法的基本含義是,定義一系列同名方法,並且可以通過引數的型別、個數等元素有效區別方法的不同版本。

在同一型別中,方法的過載在很多情況下可以使用引數預設值、引數陣列等特性來代替,而過載應用的情景是,方法的引數差異較大,無法通過引數預設值和引數陣列來處理。如下面的程式碼,我們又在C5類中新增了兩個Swap()方法,用於交換long型別和decimal型別變數的資料。

//
public static void Swap(ref long x, ref long y)
{
    long z = x;
    x = y;
    y = z;
}
//
public static void Swap(ref decimal x, ref decimal y)
{
    decimal z = x;
    x = y;
    y = z;
}

我們可以看到,Swap()方法的引數型別是不同的,但有一點,方法中的程式碼邏輯是相同的,實際上,對於這種情況,可以考慮使用泛型來處理,在後續的課程中會有討論;現在,我們需要明白的是,見到同名方法,而引數又有明顯區別時並不需要感到驚訝,它們可能只是同名的過載方法而已。

建構函式與方法鏈

我們提到過,建構函式是一種特殊的方法,那麼,特殊在什麼地方呢?主要表現為不需要設定返回值型別,以及需要和類同名。

如果在類中沒有定義建構函式,則會自動包含一個沒有任何引數的建構函式;但是,如果手工新增了任何建構函式,就不會自動新增建構函式。

建構函式也可以通過引數預設值、引數陣列、過載等形式建立多個版本,不過,我們還可以通過另外一種形式建立多版本的建構函式(或方法),如下面的程式碼,我們在CAuto類中定義了三個建構函式。

public class CAuto
{
    // 建構函式
    public CAuto(int doorCount,string model)
    {
        DoorCount = doorCount;
        Model = model;
    }
    //
    public CAuto(string model) : this(4, model) { }
    //
    public CAuto() : this("未知型號") { }
    //
}

本例中,我們首先建立了一個比較完整引數的建構函式,用於設定車門數(DoorCount)和型號(Model)。第二個建構函式包含一個引數,但其後使用this關鍵字呼叫了第一個建構函式,其含義是將車門設定為4,並設定型號。第三個建構函式使用this關鍵字呼叫了第二個建構函式,將型號設定為“未知型號”,此時,車門數量預設同樣為4。這樣逐個呼叫的建構函式就形成了方法鏈結構,實際上,不只是建構函式,其他方法同樣可以通過這種形式簡化多版本的定義。

下面的程式碼,我們在Program.cs檔案中測試這三個建構函式的使用。

static void Main(string[] args)
{
    CAuto car1 = new CAuto(2, "Coupe");
    CAuto car2 = new CAuto("CX5");
    CAuto car3 = new CAuto();
    Console.WriteLine("{0},{1}", car1.DoorCount, car1.Model);
    Console.WriteLine("{0},{1}", car2.DoorCount, car2.Model);
    Console.WriteLine("{0},{1}", car3.DoorCount, car3.Model);
}

程式碼執行結果如下圖所示。

enter image description here

解構函式

建構函式在建立物件時呼叫,而解構函式則會在刪除物件時呼叫;在.NET Framework中已經有了很成熟的記憶體回收機制,當物件不再使用時,會自動釋放記憶體,所以,需要手工編寫解構函式的時候並不多,但大家應該知道有這樣一個地方可以用來手工釋放資源。

下面的程式碼,我們在CAuto類中新增一個簡單的解構函式。

// 解構函式
~CAuto()
 {
    Console.WriteLine("汽車物件回收中");
 }

下面的程式碼,在Program.cs檔案中測試解構函式。

static void Main(string[] args)
{
     CAuto car = new CAuto();
     //car = null;
 }

程式碼中,“car= null;”語句用於手工釋放car物件,即將物件設定為null值,大家可測試一下,無論有沒有這條語句,執行結果都如下圖所示。

enter image description here

從本例中,我們可以看到,無論是手工釋放物件,還是.NET Framework自動釋放物件,都會呼叫解構函式。另一種自動釋放資源的機制是通過using語句結構呼叫實現IDisposable介面的物件,後續內容中會有介紹。

CHY軟體小屋原創作品!

相關文章