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