一、引言
對于一些初學者(包括工作幾年的人在内)來說,有時候對于方法之間的參數傳遞的問題感覺比較困惑的,因為之前在面試的過程也經常遇到參數傳遞的基礎面試題,這樣的面試題主要考察的開發人員基礎是否紮實,對于C#中值類型和引用類型有沒有深入的一個了解——這個說的了解并不是簡單的對它們簡單一個定義描述,而在于它們在記憶體中分布。是以本文章将帶領大家深入剖析下C#中參數傳遞的問題,并分享我自己的一個了解,隻有你深入了解了才能在不運作程式的情況就可以分析出參數傳遞的結果的。
二、按值傳遞
對于C#中的參數傳遞,根據參數的類型可以分為四類:
- 值類型參數的按值傳遞
- 引用類型參數的按值傳遞
- 值類型參數的按引用傳遞
- 引用類型參數的按引用傳遞
然而在預設情況下,CLR方法中參數的傳遞都是按值傳遞的。為了幫助大家全面了解參數的傳遞,下面就這四種情況一一進行分析。
2.1 值類型參數的按值傳遞
對于參數又分為:形參和實參,形參指的是被調用方法中的參數,實參指的是調用方法的參數,下面結合代碼幫助大家了解形參和實參的概念:
class Program
{
static void Main(string[] args)
{
int addNum = 1;
// addNum 就是實參,
Add(addNum);
}
// addnum就是形參,也就是被調用方法中的參數
private static void Add(int addnum)
{
addnum = addnum + 1;
Console.WriteLine(addnum);
}
}
對于值類型的按值傳遞,傳遞的是該值類型執行個體的一個拷貝,也就是形參此時接受到的是實參的一個副本,被調用方法操作是實參的一個拷貝,是以此時并不影響原來調用方法中的參數值,為了證明這點,看看下面的代碼和運作結果就明白了:
class Program
{
static void Main(string[] args)
{
// 1. 值類型按值傳遞情況
Console.WriteLine("按值傳遞的情況");
int addNum = 1;
Add(addNum);
Console.WriteLine(addNum);
Console.Read();
}
// 1. 值類型按值傳遞情況
private static void Add(int addnum)
{
addnum = addnum + 1;
Console.WriteLine(addnum);
}
運作結果為:
從結果中可以看出addNum調用方法之後它的值并沒有改變,Add 方法的調用隻是改變了addNum的副本addnum的值,是以addnum的值修改為2了。然而我們的分析到這裡并沒有結束,為了讓大家深入了解傳遞傳遞,我們有必要知道為什麼值類型參數的按值傳遞不會修改實參的值,相信下面這張圖可以解釋你所有的疑惑:
2.2 引用類型參數的按值傳遞
當傳遞的參數是引用類型的時候,傳遞和操作的是指向對象的引用(看到這裡,有些朋友會覺得此時不是傳遞引用嗎?怎麼還是按值傳遞了?對于這個疑惑,此時确實是按值傳遞,此時傳遞的對象的位址,傳遞位址本身也是傳遞這個位址的值,是以此時仍然是按值傳遞的),此時方法的操作就會改變原來的對象。對于這點可能看文字描述會比較難了解下面結合代碼和分析圖來幫助大家了解下:
class Program
{
static void Main(string[] args)
{
// 2. 引用類型按值傳遞情況
RefClass refClass = new RefClass();
AddRef(refClass);
Console.WriteLine(refClass.addnum);
}
// 2. 引用類型按值傳遞情況
private static void AddRef(RefClass addnumRef)
{
addnumRef.addnum += 1;
Console.WriteLine(addnumRef.addnum);
}
}
class RefClass
{
public int addnum=1;
}
運作結果為:
為什麼此時傳遞引用就會修改原來實參中的值呢?對于這點我們還是參數在記憶體中分布圖來解釋下:
2.3 .String引用類型的按值傳遞的特殊情況
對于String類型同樣是引用類型,然而對于string類型的按值傳遞時,此時引用類型的按值傳遞卻不會修改實參的值,可能很多朋友對于這點很困惑,下面具體看看下面的代碼:
class Program
{
static void Main(string[] args)
{
// 3. String引用類型的按值傳遞的特殊情況
string str = "old string";
ChangeStr(str);
Console.WriteLine(str);
}
// 3. String引用類型的按值傳遞的特殊情況
private static void ChangeStr(string oldStr)
{
oldStr = "New string";
Console.WriteLine(oldStr);
}
}
對于為什麼原來的值沒有被改變主要是因為string的“不變性”,是以在被調用方法中執行 oldStr="New string"代碼時,此時并不會直接修改oldStr中的"old string"值為"New string",因為string類型是不變的,不可修改的,此時記憶體會重新配置設定一塊記憶體,然後把這塊記憶體中的值修改為 “New string”,然後把記憶體中位址指派給oldStr變量,是以此時str仍然指向 "old string"字元,而oldStr卻改變了指向,它最後指向了 "New string"字元串。是以運作結果才會像上面這樣,下面記憶體分布圖可以幫助你更形象地了解文字表述:
三、按引用傳遞
不管是值類型還是引用類型,我們都可以使用ref 或out關鍵字來實作參數的按引用傳遞,然而按引用進行傳遞的時候,需要注意下面兩點:
方法的定義和方法調用都必須同時顯式使用ref或out,否則會出現編譯錯誤
CLR允許通過out 或ref參數來實作方法重載。如:
#region CLR 允許out或ref參數來實作方法重載
private static void Add(string str)
{
Console.WriteLine(str);
}
// 編譯器會認為下面的方法是另一個方法,進而實作方法重載
private static void Add(ref string str)
{
Console.WriteLine(str);
}
#endregion
按引用傳遞可以解決由于值傳遞時改變引用副本而不影響引用本身的問題,此時傳遞的是引用的引用(也就是位址的位址),而不是引用的拷貝(副本)。下面就具體看看按引用傳遞的代碼:
class Program
{
static void Main(string[] args)
{
#region 按引用傳遞
Console.WriteLine("按引用傳遞的情況");
int num = 1;
string refStr = "Old string";
ChangeByValue(ref num);
Console.WriteLine(num);
changeByRef(ref refStr);
Console.WriteLine(refStr);
#endregion
Console.Read();
}
#region 按引用傳遞
// 1. 值類型的按引用傳遞情況
private static void ChangeByValue(ref int numValue)
{
numValue = 10;
Console.WriteLine(numValue);
}
// 2. 引用類型的按引用傳遞情況
private static void changeByRef(ref string numRef)
{
numRef = "new string";
Console.WriteLine(numRef);
}
#endregion
}
從運作結果可以看出,此時引用本身的值也被改變了,通過下面一張圖來幫忙大家了解下按引用傳遞的方式:
四、總結