在探討本文的主題之前,先來介紹下C#中的值類型和引用類型 眾所周知C#中有值類型和引用類型,值類型有基礎(chǔ)數(shù)據(jù)類型(諸如int,double,bool等)、結(jié)構(gòu)體、枚舉,引用類型有接口、類、委托。 值類型全部在操作系統(tǒng)的??臻g中申請,而引用類型則在操作系統(tǒng)的堆空間中建立對象,然后在??臻g中申請一個指針指向這個對象的地址。 因此C#的引用類型其實就如同C++的指針類型。
下面我再來看看函數(shù)傳參的問題。 早在C時代就有函數(shù)參數(shù)傳值和傳地址的概念,請記住在C#中函數(shù)參數(shù)默認(rèn)都是傳值。
所以不管是值類型還是引用類型在作為參數(shù)傳進函數(shù)時,其實都是傳的值,只不過引用類型傳的是對象在堆中的的地址罷了。 而且從上面的定義可以看出C#中引用類型的變量用C++來說就相當(dāng)于是該引用類型的指針,比如有類(引用類型)RefClass: RefClass rc就相當(dāng)于是C++上的RefClass *rc 在C#中使用rc.IntValue++;時,相當(dāng)于C++的rc->IntValue++;
因為引用類型在函數(shù)傳參時是傳地址的,所以我腦袋里就形成了一種慣性思維,認(rèn)為只要傳進函數(shù)的是引用類型,那么在函數(shù)中做的任何更改都會反映到實參上。但是我發(fā)現(xiàn)并不完全是這樣,下面給出個例子(注釋內(nèi)容為對應(yīng)等效的C++代碼): using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace RefWarn { class RefClass { public int IntValue { get; set; } } class Program { static void AddValue(RefClass prc)//RefClass *prc,prc和傳進來的rc指向同一個RefClass對象的地址 { prc.IntValue++; //prc->IntValue++; } static void AddValue(ref RefClass prc)//RefClass **prc,prc指向傳進來的rc的地址 { prc.IntValue++;//(*prc)->IntValue++; } static void ChangeRef(RefClass prc)//RefClass *prc,prc和傳進來的rc指向同一個RefClass對象的地址 { prc = new RefClass() { IntValue = 1000 }; //prc=new RefClass() { IntValue = 1000 };請注意new關(guān)鍵字在C++中創(chuàng)建的是指向?qū)ο蟮闹羔槻皇菍ο?/font> } static void ChangeRef(ref RefClass prc)//RefClass **prc,prc指向傳進來的rc的地址 { prc = new RefClass() { IntValue = 1000 }; //*prc=new RefClass() { IntValue = 1000 };請注意new關(guān)鍵字在C++中創(chuàng)建的是指向?qū)ο蟮闹羔槻皇菍ο?/font> } static void Main(string[] args) { RefClass rc = new RefClass() { IntValue = 1 };//RefClass *rc=new RefClass() { IntValue = 1 };請注意new關(guān)鍵字在C++中創(chuàng)建的是指向?qū)ο蟮闹羔槻皇菍ο?/span> AddValue(rc);//rc,傳遞指向RefClass對象的指針 Console.WriteLine("調(diào)用AddValue(rc)后IntValue為:" + rc.IntValue); rc.IntValue = 1; AddValue(ref rc);//&rc,傳遞指向RefClass對象指針的指針 Console.WriteLine("調(diào)用AddValue(ref rc)后IntValue為:" + rc.IntValue); rc.IntValue = 1; ChangeRef(rc);//rc,傳遞指向RefClass對象的指針 Console.WriteLine("調(diào)用ChangeRef(rc)后IntValue為:" + rc.IntValue); rc.IntValue = 1; ChangeRef(ref rc);//&rc,傳遞指向RefClass對象指針的指針 Console.WriteLine("調(diào)用ChangeRef(ref rc)后IntValue為:" + rc.IntValue); } } } 你會發(fā)現(xiàn)在Main函數(shù)中調(diào)用ChangeRef(rc)后,rc并沒有發(fā)生改變,其屬性IntValue的值還是1。 這是為什么?我們先來看看static void ChangeRef(RefClass prc)函數(shù)的結(jié)構(gòu),看看里面都做了什么 static void ChangeRef(RefClass prc)//RefClass *prc,prc和傳進來的rc指向同一個RefClass對象的地址 可以看到函數(shù)里就是對RefClass 類型的形參引用變量prc重新賦了值。但是最后我們看到這個賦值并沒有反應(yīng)到實參引用變量rc上。原因其實很簡單就像本文開始所說的一樣,由于實參變量pc和形參變量rpc都是引用類型的變量,那么它們實際上是在操作系統(tǒng)??臻g上的兩個指針,只不過指向的是操作系統(tǒng)堆空間上的同一個RefClass 對象。在函數(shù)ChangeRef中對引用變量rpc重新賦值,相當(dāng)于是將棧中的rpc指針重新指向了堆中的另一個RefClass 對象。形參變量rpc指向的地址改變后,并不會對實參變量pc的指向發(fā)生改變,所以pc還是指向函數(shù)ChangeRef(RefClass prc)調(diào)用前的那個RefClass 對象。
但是也許你又會問為什么AddValue(rc)執(zhí)行后,函數(shù)對rc做了更改呢?我們來看看AddValue(RefClass prc)函數(shù) static void AddValue(RefClass prc)//RefClass *prc,prc和傳進來的rc指向同一個RefClass對象的地址 請注意函數(shù)AddValue并不是更改了實參引用變量rc,它更改的是rc指向的RefClass 對象的屬性,是因為實參變量pc和形參變量rpc都指向同一個RefClass 對象的原理,所以在AddValue里面rpc更改了它所指向RefClass 對象的屬性,也就等于更改了pc指向RefClass 對象的屬性。所以才在執(zhí)行AddValue(rc)后給人一種好像rpc和pc是同一個變量,更改了rpc就等于更改了pc的錯覺。但是請記住這是絕對錯誤的,rpc和pc是兩個完全不同的引用變量,只不過指向的是內(nèi)存中的同一個RefClass 對象。
最后我們來探討下有沒有辦法使函數(shù)在傳遞引用類型的參數(shù)時,讓形參完全等于實參呢?能否做到不管對形參是重新賦值還是做更改,都反映到實參上? 答案是肯定就是使用ref關(guān)鍵字 這個關(guān)鍵字用在值類型上的時候,就相當(dāng)于C++的指針類型,比如: ref int param 就相當(dāng)于C++的 int *param 且該指針指向的就是其對應(yīng)的實參變量 所以在C#中使用聲明為ref的int形參變量param.ToString()時候,相當(dāng)于C++上使用int指針*param.ToString() 所以在使用聲明為ref的int形參param時,就相當(dāng)于是C++上的*param,其操作的就是param指向的那個int變量,即實參。
而當(dāng)這個關(guān)鍵字用在引用類型前面的時候,就相當(dāng)于是指向引用類型變量的地址,而前面說過C#引用類型的變量就相當(dāng)于是C++的指針,那么指向引用類型變量的地址也相當(dāng)于就是指向指針的指針。 因為前面說了RefClass rc相當(dāng)于C++的RefClass *rc 那么ref RefClass rc相當(dāng)于C++的RefClass **rc 在C#中使用聲明為ref的RefClass變量rc.ToString()時,當(dāng)于C++上上使用RefClass指針的指針*rc->ToString() 所以在使用聲明為ref的RefClass類型形參rc時,就相當(dāng)于是C++上的*rc(注意*rc還是指針,因為rc是指向指針的指針),其操作的是形參rc指向的那個RefClass類型的引用變量(即rc指向的是實參變量的地址,而不實參變量指向堆空間中對象的地址),即實參。
而實參前面的ref相當(dāng)于是C++的&符號即取該變量的地址。
所以在函數(shù)形參前加上ref那么形參變量指向的就是實參變量的地址,只不過如果實參類型是值類型,那么形參變量指向的就是該實參變量在操作系統(tǒng)棧中的地址。如果實參是引用類型,那么形參變量指向的也是實參變量在操作系統(tǒng)棧中的地址,只不過該實參變量又指向?qū)ο笤诓僮飨到y(tǒng)堆中的地址。所以無論是引用類型還是值類型,只要在其作為形參時在前面加上ref,那么形參變量都是指向?qū)崊⒆兞康闹羔?,則操作形參變量就等于是在操作實參變量。
最后一定要清楚在引用類型做函數(shù)形參時,加上ref和不加ref的不同。 還是拿RefClass rc來舉例:
|
|
來自: herowuking > 《C#》