我們都知道所謂的程序,就是對傳入的數據進行操作,最終將處理的數據輸出給用戶。那麼在我們的編程中的體現,就是通過函數(或者方法)參數傳入數據,然後通過函數返回值輸出處理後的數據。數據的傳入涉及到函數的實參和形參的傳遞,數據的輸出涉及到函數的返回值和返回值類型的參數。函數返回值沒有什麼特別之處,而其他的都是函數傳參的問題,既然是傳參,那麼傳參到底傳過去了什麼?
函數傳參與賦值操作
我們都知道函數傳參涉及到形參和實參,那麼實參和形參就是兩個不同的變量,所以函數傳參的過程,類似(或者說就是)使用實參給形參賦值的過程。那麼我們先來分析按值傳參的情況
圖1是值類型變量的賦值操作圖,我們大家都知道值類型變量直接保存變量的內容,所以賦值的過程就是將ValA保存的內容直接拷貝到ValB中,此時兩個變量除了保存的值相同外,沒有其他的任何關系,所以此時修改ValB的內容並不會引起ValA的變化。這是與值類型按值傳參的效果是一樣的。
圖 1.值類型變量賦值
圖2展示的是引用類型變量的賦值操作,我們都知道引用類型的變量保存的是一個地址,這個地址指向了變量內容所在的地址,也就是說引用變量保存的是一個地址。那麼引用類型的賦值操作同樣也是拷貝變量保存的內容,這樣ValA和ValB就都指向了同樣地址。如果我們修改ValB的成員,那麼ValA中也會隨之產生變化。我們可以看到值類型和引用類型的賦值操作是一樣的,都是對變量直接保存的內容的拷貝過程。但是如果我們使用其他對象對ValB重新賦值,這時ValB就指向了其他對象,ValA還指向原來的對象並沒有變化。
圖 2. 引用類型變量賦值
我們知道C#中引入了ref關鍵字,使我們可以通過函數參數返回函數的處理結果,對於我們需要同時返回多項結果的時候比較有用。其實這也就是我們通常所說的按引用傳參。圖3展示了變量地址賦值操作,此時我們使用新的對象對ValB所指定的對象賦值,這是ValA也就指向了新的對象了。C#中的ref是否也是這樣操作的呢?我們看以看一下下邊的代碼和相應的IL就知道了。
圖 3. 變量地址賦值操作
C#代碼
[csharp]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace FunctionParameter
{
class PassByValue
{
public void PassValueParameterByReference(ref ReferencePara parameter)
{
parameter.a = 1;
parameter = new ReferencePara();
}
public class ReferencePara
{
public int a;
public int b;
}
}
}
[csharp]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace FunctionParameter
{
class PassByValue
{
public void PassValueParameterByReference(ref ReferencePara parameter)
{
parameter.a = 1;
parameter = new ReferencePara();
}
public class ReferencePara
{
public int a;
public int b;
}
}
}
對應的MSIL代碼
[csharp]
//傳遞的是變量的地址
.method public hidebysig instance void PassValueParameterByReference(class FunctionParameter.PassByValue/ReferencePara& parameter) cil managed
{
// 代碼大小 17 (0x11)
.maxstack 8
IL_0000: nop
//加載參數
IL_0001: ldarg.1
//加載對象的引用
IL_0002: ldind.ref
IL_0003: ldc.i4.1
IL_0004: stfld int32 FunctionParameter.PassByValue/ReferencePara::a
IL_0009: ldarg.1
IL_000a: newobj instance void FunctionParameter.PassByValue/ReferencePara::.ctor()
//綁定對象的引用
IL_000f: stind.ref
IL_0010: ret
} // end of method PassByValue::PassValueParameterByReference
[csharp] view plaincopy
//傳遞的是變量的地址
.method public hidebysig instance void PassValueParameterByReference(class FunctionParameter.PassByValue/ReferencePara& parameter) cil managed
{
// 代碼大小 17 (0x11)
.maxstack 8
IL_0000: nop
//加載參數
IL_0001: ldarg.1
//加載對象的引用
IL_0002: ldind.ref
IL_0003: ldc.i4.1
IL_0004: stfld int32 FunctionParameter.PassByValue/ReferencePara::a
IL_0009: ldarg.1
IL_000a: newobj instance void FunctionParameter.PassByValue/ReferencePara::.ctor()
//綁定對象的引用
IL_000f: stind.ref www.2cto.com
IL_0010: ret
} // end of method PassByValue::PassValueParameterByReference
總結
從我們大學一開始接觸計算機編程開始,我們就開始記憶兩種函數傳參的異同,其實傳參的過程就是實參對形參賦值的過程,只不過有時拷貝的是變量的內容,有時拷貝的是變量的地址罷了。
作者:xuhongwei0411