在**本文的主題之前,先來介紹下c#中的值型別和引用型別
眾所周知c#中有值型別和引用型別,值型別有基礎資料型別(諸如int,double,bool等)、結構體、列舉,引用型別有介面、類、委託。
值型別全部在作業系統的棧空間中申請,而引用型別則在作業系統的堆空間中建立物件,然後在棧空間中申請乙個指標指向這個物件的位址。
因此c#的引用型別其實就如同c++的指標型別。
下面我再來看看函式傳參的問題。
早在c時代就有函式引數傳值和傳位址的概念,請記住在c#中函式引數預設都是傳值。
所以不管是值型別還是引用型別在作為引數傳進函式時,其實都是傳的值,只不過引用型別傳的是物件在堆中的的位址罷了。
而且從上面的定義可以看出c#中引用型別的變數用c++來說就相當於是該引用型別的指標,比如有類(引用型別)refclass:
refclass rc就相當於是c++上的refclass *rc
在c#中使用rc.intvalue++;時,相當於c++的rc->intvalue++;
因為引用型別在函式傳參時是傳位址的,所以我腦袋裡就形成了一種慣性思維,認為只要傳進函式的是引用型別,那麼在函式中做的任何更改都會反映到實參上。但是我發現並不完全是這樣,下面給出個例子(注釋內容為對應等效的c++**):
using
system;
using
system.collections.generic;
using
system.linq;
using
system.text;
namespace
refwarn
}class
program
static
void
addvalue(
refrefclass prc)
//refclass **prc,prc指向傳進來的rc的位址
static
void
changeref(refclass prc)
//refclass *prc,prc和傳進來的rc指向同乙個refclass物件的位址;//
prc=new refclass() ;請注意new關鍵字在c++中建立的是指向物件的指標不是物件
}static
void
changeref(
refrefclass prc)
//refclass **prc,prc指向傳進來的rc的位址;//
*prc=new refclass() ;請注意new關鍵字在c++中建立的是指向物件的指標不是物件
}static
void
main(
string
args);//
refclass *rc=new refclass() ;請注意new關鍵字在c++中建立的是指向物件的指標不是物件
addvalue(rc);
//rc,傳遞指向refclass物件的指標
console.writeline(
"呼叫addvalue(rc)後intvalue為:"+
rc.intvalue);
rc.intvalue =1
;addvalue(
refrc);
//&rc,傳遞指向refclass物件指標的指標
console.writeline(
"呼叫addvalue(ref rc)後intvalue為:"+
rc.intvalue);
rc.intvalue =1
;changeref(rc);
//rc,傳遞指向refclass物件的指標
console.writeline(
"呼叫changeref(rc)後intvalue為:"+
rc.intvalue);
rc.intvalue =1
;changeref(
refrc);
//&rc,傳遞指向refclass物件指標的指標
console.writeline(
"呼叫changeref(ref rc)後intvalue為:"+
rc.intvalue);}}
}你會發現在main函式中呼叫changeref(rc)後,rc並沒有發生改變,其屬性intvalue的值還是1。
這是為什麼?我們先來看看static void changeref(refclass prc)函式的結構,看看裡面都做了什麼
static void changeref(refclass prc)//refclass *prc,prc和傳進來的rc指向同乙個refclass物件的位址
;//prc=new refclass() ;
}可以看到函式裡就是對refclass 型別的形參引用變數prc重新賦了值。但是最後我們看到這個賦值並沒有反應到實參引用變數rc上。原因其實很簡單就像本文開始所說的一樣,由於實參變數pc和形參變數rpc都是引用型別的變數,那麼它們實際上是在作業系統棧空間上的兩個指標,只不過指向的是作業系統堆空間上的同乙個refclass 物件。在函式changeref中對引用變數rpc重新賦值,相當於是將棧中的rpc指標重新指向了堆中的另乙個refclass 物件。形參變數rpc指向的位址改變後,並不會對實參變數pc的指向發生改變,所以pc還是指向函式changeref(refclass prc)呼叫前的那個refclass 物件。
但是也許你又會問為什麼addvalue(rc)執行後,函式對rc做了更改呢?我們來看看addvalue(refclass prc)函式
static void addvalue(refclass prc)//refclass *prc,prc和傳進來的rc指向同乙個refclass物件的位址
請注意函式addvalue並不是更改了實參引用變數rc,它更改的是rc指向的refclass 物件的屬性,是因為實參變數pc和形參變數rpc都指向同乙個refclass 物件的原理,所以在addvalue裡面rpc更改了它所指向refclass 物件的屬性,也就等於更改了pc指向refclass 物件的屬性。所以才在執行addvalue(rc)後給人一種好像rpc和pc是同乙個變數,更改了rpc就等於更改了pc的錯覺。但是請記住這是絕對錯誤的,rpc和pc是兩個完全不同的引用變數,只不過指向的是記憶體中的同乙個refclass 物件。
最後我們來**下有沒有辦法使函式在傳遞引用型別的引數時,讓形參完全等於實參呢?能否做到不管對形參是重新賦值還是做更改,都反映到實參上?
答案是肯定就是使用ref關鍵字
這個關鍵字用在值型別上的時候,就相當於c++的指標型別,比如:
ref int param
就相當於c++的
int *param
且該指標指向的就是其對應的實參變數
所以在c#中使用宣告為ref的int形參變數param.tostring()時候,相當於c++上使用int指標*param.tostring()
所以在使用宣告為ref的int形參param時,就相當於是c++上的*param,其操作的就是param指向的那個int變數,即實參。
而當這個關鍵字用在引用型別前面的時候,就相當於是指向引用型別變數的位址,而前面說過c#引用型別的變數就相當於是c++的指標,那麼指向引用型別變數的位址也相當於就是指向指標的指標。
因為前面說了refclass rc相當於c++的refclass *rc
那麼ref refclass rc相當於c++的refclass **rc
在c#中使用宣告為ref的refclass變數rc.tostring()時,當於c++上上使用refclass指標的指標*rc->tostring()
所以在使用宣告為ref的refclass型別形參rc時,就相當於是c++上的*rc(注意*rc還是指標,因為rc是指向指標的指標),其操作的是形參rc指向的那個refclass型別的引用變數(即rc指向的是實參變數的位址,而不實參變數指向堆空間中物件的位址),即實參。
而實參前面的ref相當於是c++的&符號即取該變數的位址。
所以在函式形參前加上ref那麼形參變數指向的就是實參變數的位址,只不過如果實參型別是值型別,那麼形參變數指向的就是該實參變數在作業系統棧中的位址。如果實參是引用型別,那麼形參變數指向的也是實參變數在作業系統棧中的位址,只不過該實參變數又指向物件在作業系統堆中的位址。所以無論是引用型別還是值型別,只要在其作為形參時在前面加上ref,那麼形參變數都是指向實參變數的指標,則操作形參變數就等於是在操作實參變數。
最後一定要清楚在引用型別做函式形參時,加上ref和不加ref的不同。
還是拿refclass rc來舉例:
C 引用型別作為函式引數時
在 本文的主題之前,先來介紹下c 中的值型別和引用型別 眾所周知c 中有值型別和引用型別,值型別有基礎資料型別 諸如int,double,bool等 結構體 列舉,引用型別有介面 類 委託。值型別全部在作業系統的棧空間中申請,而引用型別則在作業系統的堆空間中建立物件,然後在棧空間中申請乙個指標指向這...
C 引用型別作為函式引數時
在 本文的主題之前,先來介紹下c 中的值型別和引用型別 眾所周知c 中有值型別和引用型別,值型別有基礎資料型別 諸如int,double,bool等 結構體 列舉,引用型別有介面 類 委託。值型別全部在作業系統的棧空間中申請,而引用型別則在作業系統的堆空間中建立物件,然後在棧空間中申請乙個指標指向這...
C 引用作為函式引數
有了變數名,為什麼還需要乙個別名呢?c 之所以增加引用型別,主要是把它作為函式引數,以擴充函式傳遞資料的功能。到目前為止我們介紹過函式引數傳遞的兩種情況。1 將變數名作為實參和形參 這時傳給形參的是變數的值,傳遞是單向的。如果在執行函式期間形參的值發生變化,並不傳回給實參。因為在呼叫函式時,形參和實...