天天看點

C#中的參數傳遞:值類型(value type)和引用類型(reference type)

由于在.NET中存在兩種類型,分别是值類型(value type)和引用類型(reference type),是以很多關于C#中參數 傳遞 的混淆就是以而生。本文首先從值類型和引用類型的辨析入手,然後解釋了在C#中的參數 傳遞 的四種形式:值傳遞 (預設形式)、ref傳遞 、out傳遞 、params傳遞 。

首 先要弄清楚的是:值類型是配置設定在棧(stack)上面,而引用類型配置設定在堆(heap)上面。棧是一種先進後出,并且由系統自動操作的存儲空間。而堆 (在.NET上準确的說是托管堆 Managed Heap)是一種自由儲存區(Free Memory),在該區域中,必須明确的為對象申請存儲空間(一般在Java和C #中都是使用的new關鍵字),并可以在使用完以後釋放申請的存儲空間(Java和C #都使用垃圾回收機制 Garbage Collector自動釋放對象空間)

引用類型(reference type):它存放的值是指向資料的引用(reference),而不是資料本身。 示例:

System.Text.StringBuilder sb = new StringBuilder();

這 裡,我們聲明一個變量sb,并通過new StringBuilder()建立了一個StringBuilder(與Java中StringBuffer類似)對象,再将對象的引用 (reference)指派給變量sb,即變量sb中儲存的是StringBuilder對象的引用,而非對象本身。

System.Text.StringBuilder first = new StringBuilder();

System.Text.StringBuilder second = first;

這裡,我們将變量first的值(對一個StringBuilder對象的引用)指派給變量second,即first和second都指向同一個StringBuilder對象。對StringBuilder對象的任何修改都會影響到first和second變量。

System.Text.StringBuilder first = new StringBuilder();

System.Text.StringBuilder second = first;

first.Append("hello");

first = null;

Console.WriteLine(second);

這 裡,輸出的結果是 hello。由于first和second都含有對同一StringBuilder對象的引用。然後通過first的引用調用StringBuilder 對象的Append方法,将對象進行修改,即添加字元串“hello”,然後又将first指派為null,表示讓first不引用任何對象。最後通過 second的引用隐式調用StringBuilder對象的ToString方法輸出“hello”。由此可見,first的值改變了(被指派為 null),而它所引用的對象并不會發生改變,second照樣引用到StringBuilder對象。

class類型,interface類型,delegate類型和array類型都是引用類型。

值類型(value type):引用類型中變量和實際資料之間還隔了一間接層,而值類型就完全不存在,值類型的變量直接儲存的就是資料。

struct IntHolder

{

public int i;

}

這裡,結構是值類型,IntHolder是一個結構:

IntHolder first = new IntHolder();

first.i = 5;

IntHolder second = first;

first.i = 6;

Console.WriteLine(second.i);

輸出結果為5。這裡second = first 以後second儲存的是first的值拷貝,即second.i = 5;而後來的first.i發生了改變并不會影響second.i。是以輸出值為5。

簡單類型(比如int,double,char),enum類型,struct類型都是值類型。

注 意:有一些類型(比如string類型)的行為看起來像值類 型,但實際上是引用類型。這些類型被稱為immutable類型,也就是說這種類型的執行個體隻要被構造好就不會改變。比 如,string.Replace()并不會改變調用它的字元串對象,而是傳回含有新資料的新的字元串對象。

一、值參數 (Value parameters):

預設情況下,參數都是值參數。

void Foo (StringBuilder x)

{

x = null;

}

...

StringBuilder y = new StringBuilder();

y.Append ("hello");

Foo (y);

Console.WriteLine (y==null);

輸出結果為False。這裡由于是值傳遞 形式,是以盡管x被設定為null,但是y的值不會改變。但要注意的是,引用類型變量儲存的是引用,如果兩個引用類型變量引用到相同的對象,那麼對對象進行修改勢必影響到兩個引用類型變量 ,如下:

void Foo (StringBuilder x)

{

x.Append (" world");

}

...

StringBuilder y = new StringBuilder();

y.Append ("hello");

Foo (y);

Console.WriteLine (y);

輸出結果為hello world。因為在調用Foo方法以後,y所引用到的StringBuilder對象的内容被修改為“hello world”了,這是通過Foo方法中的x引用調用Append方法添加“ world”實作的。

現在考慮一下通過值類型做值參數 的情況:

void Foo (IntHolder x)

{

x.i=10;

}

...

IntHolder y = new IntHolder();

y.i=5;

Foo (y);

Console.WriteLine (y.i);

輸出結果為5。如果将IntHolder的struct類型改為class類型,則輸出結果變為10。這好了解,前者struct是值類型,傳遞的是值本身;後者class是引用類型,以傳值形式傳遞的object reference(對象引用) 。

二、引用參數 (Reference parameters):

引用參數 不傳遞 在函數成員調用中的變量的值,而是傳遞變量本身 。這就意味着它并不會為函數成員聲明中的變量配置設定新的記憶體空間,而是使用與實參相同的存儲空間,是以實參和形參的值無論什麼時候都是一樣的。要在C#中使用引用參數 ,必須在函數聲明以及函數調用中都明确地使用關鍵字ref,這樣一看代碼就很清楚知道是使用引用參數 了。

void Foo (ref StringBuilder x)

{

x = null;

}

...

StringBuilder y = new StringBuilder();

y.Append ("hello");

Foo (ref y);

Console.WriteLine (y==null);

輸出結果為True。這裡使用引用參數 ,傳遞 的是對y的引用(reference),而不是y中的值(object reference)。是以形參x值的改變馬上就反映到y上。在Foo方法調用以後,y的值也為null。

考慮一下使用struct值類型的情況:

void Foo (ref IntHolder x)

{

x.i=10;

}

...

IntHolder y = new IntHolder();

y.i=5;

Foo (ref y);

Console.WriteLine (y.i);

輸出結果為10。其中兩個變量都共享的是同一個存儲空間,對x的改變同樣影響到y。

三、輸出參數 (Output parameters):

輸出參數 與引用參數 非常相似,除了使用關鍵字out以外,它們的不同點在于ref修飾的形式參數 可以不被指派,而out修飾的形式參數 必須被指派。是以,使用輸出參數 的實參可以在調用之前不被初始化。

void Foo (out int x)

{

// Can't read x here - it's considered unassigned

// Assignment - this must happen before the method can complete normally

x = 10;

// The value of x can now be read:

int a = x;

}

...

// Declare a variable but don't assign a value to it

int y;

// Pass it in as an output parameter, even though its value is unassigned

Foo (out y);

// It's now assigned a value, so we can write it out:

Console.WriteLine (y);

四、參數 數組(Parameter arrays):

用params修飾的一維數組為參數 數組。它可以接收可變數目的實參。C /C++程式員可以認為params等同于類型安全的stdarg.h頭檔案中varargs宏。

void ShowNumbers (params int[] numbers)

{

foreach (int x in numbers)

{

Console.Write (x+" ");

}

Console.WriteLine();

}

...

int[] x = {1, 2, 3};

ShowNumbers (x);

ShowNumbers (4, 5);

輸出結果為:

1 2 3

4 5

繼續閱讀