程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 你真的了解C#中的值和引用嗎?(下)

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

編輯:C#入門知識

前兩天討論了一下關於值類型存儲位置常見的誤區,沒有想到我認為盡人皆知的秘密還是有人心存疑問。雖然我也不能舉出有力的證據證明這一點(引用類型的值類型字段存儲在堆上),但實際上這屬於實現細節。我上一篇文章想重點強調的就是,不能把實現細節當真理,因為它是不穩定的。
 
今天要討論的話題是參數傳遞,這不是實現細節。
 
參數的種類
C#中的參數共分為4種:
 
值參數(按值傳遞的參數)
引用參數(按引用傳遞的參數,使用ref修飾符)
輸出參數(使用out修飾符)
參數數組(使用params修飾符)
本文主要討論參數按值傳遞和按引用傳遞的區別,以及值類型和引用類型在按值傳遞和按引用傳遞時的表現。我們均以向方法傳遞參數為例。
 
按值傳遞的參數
C#的參數在默認情況下都是按值傳遞的。也就是說,當向方法傳遞參數的時候,會創建一個新的存儲位置,然後將參數的值復制一份放到該存儲位置中。相當於聲明了一個局部變量(實參),然後用傳入的參數的值初始化這個變量。如果在方法內改變實參的值,將不會影響到方法調用的上下文。
 
上篇文章提到過,值類型表達式的值是數據本身,引用類型表達式的值是到對象的引用。所以,按值傳遞的時候,對值類型的參數來說,復制的是該值類型參數所存儲的數據;對引用類型的參數來說,復制的值是到具體對象的引用。(注意,這和直接用變量對另一個變量賦值是一樣的。)
 
值類型按值傳遞
注意如剛才所說,值類型按值傳遞時,將復制該值類型本身所代表的數據。如下面的代碼片段:
 
// #Code1
int i = 5;
N(i);
Console.WriteLine(i);
...
void N(int j)
{
    j = 10;
}雖然在N內部,j被設置為10,但實際上在方法調用的上下文中,i的值仍然是5,改變的是i的一個副本j。如下圖所示:
 
 image
 
引用類型按值傳遞
對引用類型的參數來說,復制的值是到具體對象的引用。如下面的代碼片段:
 
// #Code2
StringBuilder sb1 = new StringBuilder("Hello");
M(sb1);
Console.WriteLine(sb1);
...
void M(StringBuilder sb2)
{
    sb2 = null;
}雖然在M內部,sb2被設置為null,但實際的輸出結果仍然是“Hello”。如下圖所示:
 
 image
 
注意,我們說的是在方法內部改變實參的值,不會對外部調用上下文產生影響。對於引用類型的實參,如果在方法內部更改所引用的對象的數據,實參和外部變量仍然引用的是同樣的對象,因此都會受到影響。例如下面的代碼:
 
// #Code3
StringBuilder sb1 = new StringBuilder("Hello");
M(sb1);
Console.WriteLine(sb1);
...
void M(StringBuilder sb2)
{
    sb2.Append(" world");
}輸出結果將為“Hello world”。如下圖所示:
 
 
 image
按引用傳遞的參數
按引用傳遞不會涉及隱式復制。它所傳遞的,不是在調用方法時傳遞給方法的變量的值,而是變量本身。它不會創建新的存儲位置,而是使用與變量相同的存儲位置,因此,在調用方法上下文中傳遞給方法的變量與方法內部使用的參數,實際上是同一個。
 
在按引用傳遞參數時,在方法的聲明和調用的地方都必須顯式使用ref修飾符,這是為了讓你清楚你正在進行的是與默認傳遞方式不同的按引用傳遞。
 image
值類型按引用傳遞
按照引用傳遞的定義,我們實際上是把變量本身傳遞給了方法,在方法內和調用方法的地方,使用的實際上是同一個變量。因此對於值類型來說,在方法內部對參數所做的任何改動,也都會反映到方法外部。#Code1改成按引用傳遞後,輸出結果將為10。
 
// #Code4
int i = 5;
N(ref i);
Console.WriteLine(i);
...
void N(ref int j)
{
    j = 10;
}參數的傳遞過程如下圖所示:
 
 
 
引用類型按引用傳遞
應用類型按應用傳遞與值類型按引用傳遞表現形式是一樣的,在方法內部所做的任何改變,都將反映到外部變量上。因此,如#Code2將sb2設置為null,則外部的sb1也會變成null,如#Code3調用sb2.Append,外部的sb1也會進行相應的改變。
 
// #Code5
StringBuilder sb1 = new StringBuilder("Hello");
M(ref sb1);
Console.WriteLine(sb1);
...
void M(ref StringBuilder sb2)
{
    sb2 = null;
}如圖所示:
 image
 
 
結論
我們通常提到C#中的值和引用,大多數情況可能都是指值類型和引用類型,但實際上值和引用有著更加豐富的含義。我這兩篇文章試圖把這些概念總結出來,講解了值類型和引用類型的值是什麼,以及它們在按值傳遞和按參數傳遞時有什麼相同和不同之處。更重要的是,關於值類型和引用類型,我們平時在認識上存在很多誤區,把相關的實現細節作為知識點記憶了下來。其實這些只是其然,不是所以然。而所以然,我們也沒必要去深究。
 
很多面試官在考察面試者時,也會不自覺得去問一些實現細節,這完全沒有必要。(當然,很可能面試官也陷入了這樣的誤區。)以後我們應該牢記的就是,當提到引用類型和值類型區別的時候,如果有人扯到存儲位置這個話題上來(90%的人都大概會這樣吧),你應該把他們引導到正確的方向上來。
 
參考資料
Parameter passing in C#


作者 麒麟.NET

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved