1. 程式人生 > >你真的瞭解C#中的值和引用嗎?(下)

你真的瞭解C#中的值和引用嗎?(下)

轉載自:http://www.cnblogs.com/kirinboy/archive/2012/06/15/value-and-reference-in-csharp-2.html

引數的種類

C#中的引數共分為4種:

  • 值引數(按值傳遞的引數)
  • 引用引數(按引用傳遞的引數,使用ref修飾符)
  • 輸出引數(使用out修飾符)
  • 引數陣列(使用params修飾符)

本文主要討論引數按值傳遞和按引用傳遞的區別,以及值型別和引用型別在按值傳遞和按引用傳遞時的表現。我們均以向方法傳遞引數為例。

按值傳遞的引數

C#的引數在預設情況下都是按值傳遞的。也就是說,當向方法傳遞引數的時候,會建立一個新的儲存位置,然後將引數的值複製一份放到該儲存位置中。相當於聲明瞭一個區域性變數(實參),然後用傳入的引數的值初始化這個變數。如果在方法內改變實參的值,將不會影響到方法呼叫的上下文。

上篇文章提到過,值型別表示式的值是資料本身,引用型別表示式的值是到物件的引用。所以,按值傳遞的時候,對值型別的引數來說,複製的是該值型別引數所儲存的資料;對引用型別的引數來說,複製的值是到具體物件的引用。(注意,這和直接用變數對另一個變數賦值是一樣的。)

值型別按值傳遞

注意如剛才所說,值型別按值傳遞時,將複製該值型別本身所代表的資料。如下面的程式碼片段:

// #Code1int i =5;
N(i);Console.WriteLine(i);...void N(int j){
    j =10;}

雖然在N內部,j被設定為10,但實際上在方法呼叫的上下文中,i的值仍然是5,改變的是i的一個副本j

。如下圖所示:

image

引用型別按值傳遞

對引用型別的引數來說,複製的值是到具體物件的引用。如下面的程式碼片段:

// #Code2StringBuilder sb1 =newStringBuilder("Hello");
M(sb1);Console.WriteLine(sb1);...void M(StringBuilder sb2){
    sb2 =null;}

雖然在M內部,sb2被設定為null,但實際的輸出結果仍然是“Hello”。如下圖所示:

image

注意,我們說的是在方法內部改變實參的,不會對外部呼叫上下文產生影響。對於引用型別的實參,如果在方法內部更改所引用的物件的資料,實參和外部變數仍然引用的是同樣的物件,因此都會受到影響。例如下面的程式碼:

// #Code3StringBuilder sb1 =newStringBuilder("Hello");
M(sb1);Console.WriteLine(sb1);...void M(StringBuilder sb2){
    sb2.Append(" world");}

輸出結果將為“Hello world”。如下圖所示:

image

按引用傳遞的引數

按引用傳遞不會涉及隱式複製。它所傳遞的,不是在呼叫方法時傳遞給方法的變數的值,而是變數本身。它不會建立新的儲存位置,而是使用與變數相同的儲存位置,因此,在呼叫方法上下文中傳遞給方法的變數與方法內部使用的引數,實際上是同一個。

在按引用傳遞引數時,在方法的宣告和呼叫的地方都必須顯式使用ref修飾符,這是為了讓你清楚你正在進行的是與預設傳遞方式不同的按引用傳遞。

值型別按引用傳遞

按照引用傳遞的定義,我們實際上是把變數本身傳遞給了方法,在方法內和呼叫方法的地方,使用的實際上是同一個變數。因此對於值型別來說,在方法內部對引數所做的任何改動,也都會反映到方法外部。#Code1改成按引用傳遞後,輸出結果將為10。

// #Code4int i =5;
N(ref i);Console.WriteLine(i);...void N(refint j){
    j =10;}

引數的傳遞過程如下圖所示:

image

引用型別按引用傳遞

應用型別按應用傳遞與值型別按引用傳遞表現形式是一樣的,在方法內部所做的任何改變,都將反映到外部變數上。因此,如#Code2將sb2設定為null,則外部的sb1也會變成null,如#Code3呼叫sb2.Append,外部的sb1也會進行相應的改變。

// #Code5StringBuilder sb1 =newStringBuilder("Hello");
M(ref sb1);Console.WriteLine(sb1);...void M(refStringBuilder sb2){
    sb2 =null;}

如圖所示:

image

結論

我們通常提到C#中的值和引用,大多數情況可能都是指值型別和引用型別,但實際上值和引用有著更加豐富的含義。我這兩篇文章試圖把這些概念總結出來,講解了值型別和引用型別的值是什麼,以及它們在按值傳遞和按引數傳遞時有什麼相同和不同之處。更重要的是,關於值型別和引用型別,我們平時在認識上存在很多誤區,把相關的實現細節作為知識點記憶了下來。其實這些只是其然,不是所以然。而所以然,我們也沒必要去深究。

很多面試官在考察面試者時,也會不自覺得去問一些實現細節,這完全沒有必要。(當然,很可能面試官也陷入了這樣的誤區。)以後我們應該牢記的就是,當提到引用型別和值型別區別的時候,如果有人扯到儲存位置這個話題上來(90%的人都大概會這樣吧),你應該把他們引導到正確的方向上來。