對於一些初學者(包括工做幾年的人在內)來講,有時候對於方法之間的參數傳遞的問題感受比較困惑的,由於以前在面試的過程也常常遇到參數傳遞的基礎面試題,這樣的面試題主要考察的開發人員基礎是否紮實,對於C#中值類型和引用類型有沒有深刻的一個理解——這個說的理解並非簡單的對它們簡單一個定義描述,而在於它們在內存中分佈。因此本文章將帶領你們深刻剖析下C#中參數傳遞的問題,並分享我本身的一個理解,只有你深刻理解了才能在不運行程序的狀況就能夠分析出參數傳遞的結果的。面試
對於C#中的參數傳遞,根據參數的類型能夠分爲四類:c#
值類型參數的按值傳遞ide
引用類型參數的按值傳遞spa
值類型參數的按引用傳遞指針
引用類型參數的按引用傳遞對象
然而在默認狀況下,CLR方法中參數的傳遞都是按值傳遞的。爲了幫助你們全面理解參數的傳遞,下面就這四種狀況一一進行分析。內存
對於參數又分爲:形參和實參,形參指的是被調用方法中的參數,實參指的是調用方法的參數,下面結合代碼幫助你們理解形參和實參的概念:開發
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了。然而咱們的分析到這裏並無結束,爲了讓你們深刻理解傳遞傳遞,咱們有必要知道爲何值類型參數的按值傳遞不會修改實參的值,相信下面這張圖能夠解釋你全部的疑惑:
當傳遞的參數是引用類型的時候,傳遞和操做的是指向對象的引用(看到這裏,有些朋友會以爲此時不是傳遞引用嗎?怎麼仍是按值傳遞了?對於這個疑惑,此時確實是按值傳遞,此時傳遞的對象的地址,傳遞地址自己也是傳遞這個地址的值,因此此時仍然是按值傳遞的),此時方法的操做就會改變原來的對象。對於這點可能看文字描述會比較難理解下面結合代碼和分析圖來幫助你們理解下:
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; }
運行結果爲:
爲何此時傳遞引用就會修改原來實參中的值呢?對於這點咱們仍是參數在內存中分佈圖來解釋下:
對於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 }
運行結果爲:
從運行結果能夠看出,此時引用自己的值也被改變了,經過下面一張圖來幫忙你們理解下按引用傳遞的方式:
到這裏參數的傳遞全部內容就介紹完了。總之,對於按值傳遞,不論是值類型仍是引用類型的按值傳遞,都是傳遞實參的一個拷貝,只是值類型時,此時傳遞的是實參實例的一個拷貝(也就是值類型值的一個拷貝),而引用類型時,此時傳遞的實參引用的副本。對於按引用傳遞,傳遞的都是參數地址,也就是實例的指針。