1. 程式人生 > >C#值參數和引用參數

C#值參數和引用參數

write pub 16px 表現 nbsp 修飾 一個 null mem

一、值參數

未用ref或out修飾符聲明的參數為值參數。

使用值參數,通過將實參的值復制到形參的方式,把數據傳遞到方法。方法被調用時,系統做如下操作。

  • 在棧中為形參分配空間。
  • 復制實參到形參。

值參數的實參不一定是變量。它可以是任何能計算成相應數據類型的表達式。

看一個例子:

float func1(float val)    //聲明方法
{
  float j=2.6F;
  float k=5.1F;
  ....
}

下面來調用方法

float fValue1=func1(k);        //實參是float類型的變量

float fValue2=func1((k+j)/3);  //實參可以計算成float表達式

在把變量作用於實參之前,變量必須賦值(除非是out參數)。對於引用類型,變量可以被設置為一個實際的引用或null。

下面的代碼展示了一個名為MyMethod的方法,它有兩個參數,一個是MyClass型變量和一個int。

 1  class MyClass
 2     {
 3         public int Val = 20;
 4     }
 5     class Program
 6     {
 7 
 8         static void MyMethod(MyClass f1, int f2)
 9         {
10             f1.Val = f1.Val + 5
; 11 f2 = f2 + 5; 12 Console.WriteLine("f1.Val: {0}, f2: {1}", f1.Val, f2); 13 } 14 static void Main(string[] args) 15 { 16 MyClass a1 = new MyClass(); 17 int a2 = 10; 18 19 MyMethod(a1, a2); 20 21 Console.WriteLine("
f1.Val: {0}, f2: {1}", a1.Val, a2); 22 } 23 }

我們用圖來表示實參和形參在方法執行的不同階段的值。

  • 在方法被調用前,用作實參的a2已經在棧裏了。
  • 在方法開始前,系統在棧中為形參分配空間,並從實參復制值。
  • 因為a1是引用類型,所以引用被復制,結果實參和形參都引用堆中的同一對象。
  • 因為a2是值類型,所以值被復制,產生了一個獨立的數據項。
  • 在方法的結尾,f2和對象f1的字段都被加上了5。
  • 方法執行後,形參從棧中彈出。
  • a2,值類型,它的值不受方法行為的影響。
  • a1,引用類型,但它的值被方法的行為改變了。

技術分享

二、引用參數

使用引用參數時,必須在方法的申明和調用中都使用關鍵字ref修飾符。

實參必須是變量,在用作實參前必須被賦值。如果是引用類型的變量,可以賦值為一個引用或者null值。

下面的代碼闡明了引用參數的聲明和調用的語法:

  void MyMethod(ref int val)  //方法聲明包含ref修飾符
  {
     //your code
   }

  int y = 1;
  MyMethod(ref y);   //方法調用

  MyMethod(ref 3+5);  //錯誤,形參必須是變量

在第一小節的內容中我們知道,對於值參數,系統在棧上為形參分配內存,相反對於引用參數:

  • 不會為形參在棧上分配內存。
  • 實際情況是,形參的參數名將作為實參變量的別名,指向相同的內存位置

由於形參名和實參名的行為,就好象指向相同的內存位置,所以在方法的執行過程中,對形參作的任何改變,在方法完成後依然有效(表現在實參變量上)。

在方法的聲明和調用上都使用關鍵字ref.

下面的代碼再次展示了方法MyMethod,但這一次參數是引用參數而不是值參數。

 1  class MyClass
 2     {
 3         public int Val = 20;
 4     }
 5     class Program
 6     {
 7 
 8         static void MyMethod(ref  MyClass f1,ref int f2)
 9         {
10             f1.Val = f1.Val + 5;
11             f2 = f2 + 5;
12             Console.WriteLine("f1.Val: {0}, f2: {1}", f1.Val, f2);
13         }
14         static void Main(string[] args)
15         {
16             MyClass a1 = new MyClass();
17             int a2 = 10;
18 
19             MyMethod(ref a1, ref a2);
20 
21             Console.WriteLine("f1.Val: {0}, f2: {1}", a1.Val, a2);
22 
23         }
24     }

同樣,還是用圖來闡明方法執行的不同階段實參和形參的值。

  • 在方法被調用前,用作實參的a1,a2已經在棧裏了。
  • 在方法的開始,形參名被設置為實參的別名。變量a1和f1引用相同的內存位置,a2和f2引用相同的內存位置。
  • 在方法的結束位置,f2和對象f1的字段都被加上了5。
  • 方法執行之後,形參的名稱已經失效,但是值類型a2和引用類型a1所指向的對象的值都被方法內的行為改變了。

技術分享

三、引用類型作為值參數和引用參數

對於一個引用類型對象,不管是將其作為值參數傳遞還是作為引用參數傳遞,我們都可以在方法成員內部修改它的成員。不過,我們並沒有在方法內部設置形參本身。

下面我們就來看看在方法內部設置形參本身時會發生什麽。

1、將引用類型對象作為值參數傳遞

 1  class MyClass
 2     {
 3         public int Val = 20;
 4     }
 5     class Program
 6     {
 7 
 8         static void RefAsParameter(MyClass f1)
 9         {
10             f1.Val = 50;
11             Console.WriteLine("After member assignment:   {0}", f1.Val);
12             f1 = new MyClass();
13             Console.WriteLine("After new object creation: {0}", f1.Val);
14         }
15         static void Main(string[] args)
16         {
17 
18             MyClass a1 = new MyClass();
19             Console.WriteLine("Before method  call:       {0}", a1.Val);
20             RefAsParameter(a1);
21             Console.WriteLine("After method  call:        {0}", a1.Val);
22         }
23     }

這段代碼的輸出如下:

Before method  call:       20
After member assignment:   50
After new object creation: 20
After method  call:        50

同樣,還是用圖來闡明以下幾點。

  • 在方法開始時,實參和形參都指向堆中相同的對象。
  • 在為對象的成員賦值之後,他們仍指向堆中相同的對象。
  • 當方法分配新的對象並賦值給形參時,方法外部的實參仍指向原始對象,而形參指向的是新對象。
  • 在方法調用之後,實參指向原始對象,形參和新對象都會消失。

技術分享

2、將引用類型對象作為引用參數傳遞

除了在方法聲明和方法調用時使用ref關鍵字之外,與上面的代碼完全一樣。

 1   class MyClass
 2     {
 3         public int Val = 20;
 4     }
 5     class Program
 6     {
 7 
 8         static void RefAsParameter(ref MyClass f1)
 9         {
10             f1.Val = 50;
11             Console.WriteLine("After member assignment:   {0}", f1.Val);
12             f1 = new MyClass();
13             Console.WriteLine("After new object creation: {0}", f1.Val);
14         }
15         static void Main(string[] args)
16         {
17 
18             MyClass a1 = new MyClass();
19             Console.WriteLine("Before method  call:       {0}", a1.Val);
20             RefAsParameter(ref a1);
21             Console.WriteLine("After method  call:        {0}", a1.Val);
22         }
23     }

這段代碼的輸出如下:

Before method  call:       20
After member assignment:   50
After new object creation: 20
After method  call:        20

我們開始說過,引用參數的行為就是將實參作為形參的別名。

  • 在方法開始時,實參和形參都指向堆中相同的對象。
  • 在為對象的成員賦值之後,他們仍指向堆中相同的對象。
  • 當方法分配新的對象並賦值給形參時,形參和實參都指向新對象。
  • 在方法調用之後,實參指向方法內創建的新對象

技術分享

四、寫在最後

這些都是老生常談的問題,為什麽還要寫?

一是因為今天看書看到了與此相關的內容,回去翻了翻書,然後記錄下來

二是供自己以後查閱,畢竟看博客比翻書來的快。

最後,祝大家周末愉快,玩的開心。

C#值參數和引用參數