C# 函式引數傳遞(按值和引用)
先來說下C#中的資料型別.分值型別和引用型別兩大類.
值型別:直接儲存資料的值,儲存在記憶體中的stack(堆疊)中
引用型別:儲存對值的引用,實際上儲存的就是一個記憶體的地址.引用型別的儲存分成兩塊,實際值儲存在託管堆(heap)中.實際值的記憶體地址儲存在stack中
當使用引用型別時先找到stack中的地址,再找到heap中的實際值.
也就是說儲存引用型別時要用到stack和heap,但使用引用型別時我們實際上只用到stack中的值,然後通過這個值間接的訪問heap中的值
C#預定義的簡單型別,像int,float,bool,char都是值型別,另外enum(列舉),struct(結構)也是值型別
string,陣列,自定義的class就都是引用型別了.其中的string是比較特殊的引用型別.C#給它增加個字元恆定的特性.
C#函式的引數如果不加ref,out這樣的修飾符顯式申明引數是通過引用傳遞外,預設都是值傳遞.
這裡要注意的一個問題是,引數的型別是值型別還是引用型別和傳引數時用值傳遞還是引用傳遞是兩個不同的概念.
假如有void FunTest(int [] array) 和void FunTest(int a)
這兩個函式.引數array是引用型別,a是值型別.但是他們傳遞時都是按值傳遞.
我們來舉個例子說明下
按值傳遞引數:
class Program
{
public static void ChangeInt(int num)
{
num = 123;
}
public static void ChangeArray(int[] array)
{
array[0] = 10;
array = new int[] { 6, 7, 8, 9 };
}
static void Main(string[] args)
{
int anum = 1;
int[] aarray = { 1, 2, 3 };
ChangeInt(anum);
ChangeArray(aarray);
Console.WriteLine("value of num: " + anum);
Console.Write("value of aarray: ");
foreach (int i in aarray)
Console.Write(i + " ");
}
}
結果是:value of anum : 1
value of aarray :10 2 3
可能看到結果會有點奇怪.我們一般認為值傳遞就是把值拷貝一份,然後不管在函式中對傳入的引數做啥改變,引數之前的值不會受啥影響,所以anum沒有變成123,仍然是1
但是aarray[0]為啥卻變成10了呢?
前面我們有說到引用型別在記憶體中是儲存為兩個部分,一個是stack中的記憶體地址,另一個是heap中的實際值.用時我們只直接用stack中的值,我們假如stack中的值為0xabcdefgh
,就說是aaraay指向它吧. 那麼我們按值傳遞時就是把這個stack的值拷貝成另一份就假如是array指向它吧.跟拷貝anum的值1一樣.
但是我們操作記憶體地址這樣的值時不會像整數一樣直接操作它,而只會通過它去找heap中的實際值.
於是我們array[0] = 10.改變了實際上還是heap中陣列的值了. 但array = new int []
{6,7,8,9}沒有對之前傳的aarray產生影響.這個操作的意義是在heap中重新開闢一塊記憶體,儲存著值6,7,8,9.
這這塊記憶體的地址賦給array,於是它之前的值0xabcdefgh被改寫了.但aarray指的值stack值仍沒變,仍是0xabcdefgh
按引用傳遞引數
可以用out或ref顯式指定.它們大部分時候可以通用,只是有一點細小區別.
先用ref 來舉例吧,還用上面的例子,只是加個了關鍵字ref
class Program
{
public static void ChangeInt(ref int num)
{
num = 123;
}
public static void ChangeArray(ref int[] array)
{
array[0] = 10;
array = new int[] { 6, 7, 8, 9 };
}
static void Main(string[] args)
{
int anum = 1;
int[] aarray = { 1, 2, 3 };
ChangeInt(ref anum);
ChangeArray(ref aarray);
Console.WriteLine("value of num: " + anum);
Console.Write("value of aarray: ");
foreach (int i in aarray)
Console.Write(i + " ");
}
}
結果是:value of anum : 123
value of aarray :6 7 8 9
跟按值傳遞的結果完全不同吧
num = 123我們是容易理解.我們再來說下aarray的值為啥變了吧
按引用傳遞時aarray指向的stack中的值不會複製一份,而是直接傳過去.這樣array[0]= 10這樣賦值時也同樣改變了heap中 1 2 3
的值,變為10 2 3,如果
沒有array = new int [] {6,7,8,9}
這個語句,則它的結果跟上面按值傳遞是完全一樣的.但有個這句話後就不一樣,我們知道上面說了它的含義,在heap中開闢一塊新記憶體
值是6 7 8 9,而aarray指向的stack的值被改寫了,改為指向儲存6 7 8 9的記憶體地址了.那含有10 2
3的那一塊記憶體其實還繼續存在,只是沒有誰引用到它了.到時垃圾回收器會把它回收的.
補充:
說下out 和ref的細小區別
ref 傳進來的引數必須要先賦值.
像上面 的例子中如果這樣寫
int num;
ChangeInt(ref int num);
就會出錯,必須先給num給個值1.
而且out傳進來的引數可以不先賦值.
out num;
ChangeInt(out int num);是對的
另外還有個區別就是如果用out的時候ChangeInt函式中必須有某個地方給num賦值了,而用ref不一定需要在函式中給num賦值
其實這樣做的目的很好理解.C#為了確保在任何情況下num必須有個值,不能為空.
因為用ref,在呼叫函式前必須保證引數有值,所以在函式中就不必要求它一定再賦值
而用out由於在呼叫函式前不用保證引數必須有值,所以在函式中必須保證給它個值
ChangeInt(ref int num)和ChangeInt(out int num)雖然不一樣,但是不同共存,不能當作兩個不同的函式
而ChangeInt(int num)和上面 的兩個函式是完全不一樣的,可以放到一起共存
這樣的話呼叫的時候ref ,out這樣的關鍵字不能省的.必須匹配
轉自:https://jingyan.baidu.com/article/15622f2448c428fdfcbea5e4.html