C#中传值调用和传引用调用的理解

http://www.cnblogs.com/wang_yb/archive/2011/05/18/2050574.html

  • 如果传递的参数是基元类型(int,float等)或结构体(struct),那么就是传值调用。
  • 如果传递的参数是类(class)那么就是传引用调用。 (我觉得也是传值调用,传递了一个拷贝的地址副本,形参是实参指向同一个对象。)
  • 如果传递的参数前有ref或者out关键字,那么就是传引用调用。 (这个才是真正的传引用,形参和实参是同一个,他们的地址也是同一个。)

验证示例的代码如下:

  
  
  
  
  1. using System;    
  2.  
  3. public class ArgsByRefOrValue    
  4. {    
  5.     public static void Main(string[] args)    
  6.     {    
  7.         // 实验1. 传值调用--基元类型    
  8.         int i = 10;    
  9.         Console.WriteLine("before call ChangeByInt: i = " + i.ToString());    
  10.         ChangeByInt(i);    
  11.         Console.WriteLine("after call ChangeByInt: i = " + i.ToString());    
  12.         Console.WriteLine("==============================================");    
  13.  
  14.         // 实验2. 传值调用--结构体    
  15.         Person_val p_val = new Person_val();    
  16.         p_val.name = "old val name";    
  17.         Console.WriteLine("before call ChangeByStruct: p_val.name = " + p_val.name);    
  18.         ChangeByStruct(p_val);    
  19.         Console.WriteLine("after call ChangeByStruct: p_val.name = " + p_val.name);    
  20.         Console.WriteLine("==============================================");    
  21.  
  22.         // 实验3. 传引用调用--类    
  23.         Person_ref p_ref = new Person_ref();    
  24.         p_ref.name = "old ref name";    
  25.         Console.WriteLine("before call ChangeByClass: p_ref.name = " + p_ref.name);    
  26.         ChangeByClass(p_ref);    
  27.         Console.WriteLine("after call ChangeByClass: p_ref.name = " + p_ref.name);    
  28.         Console.WriteLine("==============================================");    
  29.  
  30.         // 实验4. 传引用调用--利用ref    
  31.         Person_ref p = new Person_ref();    
  32.         p.name = "old ref name";    
  33.         Console.WriteLine("before call ChangeByClassRef: p.name = " + p.name);    
  34.         ChangeByClassRef(ref p);    
  35.         Console.WriteLine("after call ChangeByClassRef: p.name = " + p.name);    
  36.         Console.ReadKey(true);    
  37.     }    
  38.  
  39.      
  40.  
  41.     static void ChangeByInt(int i)    
  42.     {    
  43.         i = i + 10;    
  44.         Console.WriteLine("when calling ChangeByInt: i = " + i.ToString());    
  45.     }    
  46.  
  47.     static void ChangeByStruct(Person_val p_val)    
  48.     {    
  49.         p_val.name = "new val name";    
  50.         Console.WriteLine("when calling ChangeByStruct: p_val.name = " + p_val.name);    
  51.     }    
  52.  
  53.      
  54.     static void ChangeByClass(Person_ref p_ref)    
  55.     {    
  56.         p_ref.name = "new ref name";    
  57.         Console.WriteLine("when calling ChangeByClass: p_ref.name = " + p_ref.name);    
  58.     }    
  59.  
  60.     static void ChangeByClassRef(ref Person_ref p)    
  61.     {    
  62.         p.name = "new ref name";    
  63.         Console.WriteLine("when calling ChangeByClassRef: p.name = " + p.name);    
  64.     }    
  65. }    
  66.  
  67.      
  68.  
  69. public struct Person_val    
  70. {    
  71.     public string name;    
  72. }    
  73.  
  74.      
  75.  
  76. public class Person_ref    
  77. {    
  78.     public string name;    
  79. }   

运行结果如下:

看起来似乎上面代码中实验3实验4是一样的,即对于类(class)来说,不管加不加ref或out,都是传引用调用。

其实,这只是表面的现象,只要稍微改一下代码,结果就不一样了。

修改上面代码,再增加两个实验。

  
  
  
  
  1. using System;    
  2. public class ArgsByRefOrValue    
  3. {    
  4.     public static void Main(string[] args)    
  5.     {    
  6.         // 实验1. 传值调用--基元类型    
  7.         int i = 10;    
  8.         Console.WriteLine("before call ChangeByInt: i = " + i.ToString());    
  9.         ChangeByInt(i);    
  10.         Console.WriteLine("after call ChangeByInt: i = " + i.ToString());    
  11.         Console.WriteLine("==============================================");    
  12.           
  13.         // 实验2. 传值调用--结构体    
  14.         Person_val p_val = new Person_val();    
  15.         p_val.name = "old val name";    
  16.         Console.WriteLine("before call ChangeByStruct: p_val.name = " + p_val.name);    
  17.         ChangeByStruct(p_val);    
  18.         Console.WriteLine("after call ChangeByStruct: p_val.name = " + p_val.name);    
  19.         Console.WriteLine("==============================================");    
  20.  
  21.         // 实验3. 传引用调用--类    
  22.         Person_ref p_ref = new Person_ref();    
  23.         p_ref.name = "old ref name";    
  24.         Console.WriteLine("before call ChangeByClass: p_ref.name = " + p_ref.name);    
  25.         ChangeByClass(p_ref);    
  26.         Console.WriteLine("after call ChangeByClass: p_ref.name = " + p_ref.name);    
  27.         Console.WriteLine("==============================================");    
  28.  
  29.         // 实验4. 传引用调用--利用ref    
  30.         Person_ref p = new Person_ref();    
  31.         p.name = "old ref name";    
  32.         Console.WriteLine("before call ChangeByClassRef: p.name = " + p.name);    
  33.         ChangeByClassRef(ref p);    
  34.         Console.WriteLine("after call ChangeByClassRef: p.name = " + p.name);    
  35.         Console.WriteLine("==============================================");    
  36.  
  37.         // 实验5. 传引用调用--类 在调用的函数重新new一个对象    
  38.         Person_ref p_ref_new = new Person_ref();    
  39.         p_ref_new.name = "old new ref name";    
  40.         Console.WriteLine("before call ChangeByClassNew: p_ref_new.name = " + p_ref_new.name);    
  41.         ChangeByClassNew(p_ref_new);    
  42.         Console.WriteLine("after call ChangeByClassNew: p_ref_new.name = " + p_ref_new.name);    
  43.         Console.WriteLine("==============================================");    
  44.  
  45.         // 实验6. 传引用调用--利用ref 在调用的函数重新new一个对象    
  46.         Person_ref p_new = new Person_ref();    
  47.         p_new.name = "old new ref name";    
  48.         Console.WriteLine("before call ChangeByClassRefNew: p_new.name = " + p_new.name);    
  49.         ChangeByClassRefNew(ref p_new);    
  50.         Console.WriteLine("after call ChangeByClassRefNew: p_new.name = " + p_new.name);    
  51.         Console.ReadKey(true);    
  52.     }    
  53.  
  54.      
  55.  
  56.     static void ChangeByInt(int i)    
  57.     {    
  58.         i = i + 10;    
  59.         Console.WriteLine("when calling ChangeByInt: i = " + i.ToString());    
  60.     }    
  61.  
  62.     static void ChangeByStruct(Person_val p_val)    
  63.     {    
  64.         p_val.name = "new val name";    
  65.         Console.WriteLine("when calling ChangeByStruct: p_val.name = " + p_val.name);    
  66.     }    
  67.  
  68.      
  69.     static void ChangeByClass(Person_ref p_ref)    
  70.     {    
  71.         p_ref.name = "new ref name";    
  72.         Console.WriteLine("when calling ChangeByClass: p_ref.name = " + p_ref.name);    
  73.     }    
  74.  
  75.      
  76.     static void ChangeByClassRef(ref Person_ref p)    
  77.     {    
  78.         p.name = "new ref name";    
  79.         Console.WriteLine("when calling ChangeByClassRef: p.name = " + p.name);    
  80.     }    
  81.  
  82.     static void ChangeByClassNew(Person_ref p_ref_new)    
  83.     {    
  84.         p_ref_new = new Person_ref();    
  85.         p_ref_new.name = "new ref name";    
  86.         Console.WriteLine("when calling ChangeByClassNew: p_ref_new.name = " + p_ref_new.name);    
  87.     }    
  88.  
  89.      
  90.  
  91.     static void ChangeByClassRefNew(ref Person_ref p_new)    
  92.     {    
  93.         p_new = new Person_ref();    
  94.         p_new.name = "new ref name";    
  95.         Console.WriteLine("when calling ChangeByClassRefNew: p_new.name = " + p_new.name);    
  96.     }    
  97. }    
  98.  
  99.      
  100.  
  101. public struct Person_val    
  102. {    
  103.     public string name;    
  104. }    
  105.     
  106.  
  107. public class Person_ref    
  108. {    
  109.     public string name;    
  110. }   

则运行结果为:

实验5的运行结果似乎说明即使参数是类(class),只要不加ref,也是传值调用。

下面就引出了我的理解。

2. 没有ref时,即使参数为引用类型(class)时,也可算是一种传值调用

参数为引用类型时,传递的是该引用类型的地址的一份拷贝,“该引用类型的地址的一份拷贝”即为传值调用的“值”。

注意这里说传递的是该引用类型的地址的一份拷贝,而不是引用类型的地址。

下面将用图的形式来说明以上实验3,实验5和实验6中内存的情况。

2.1 首先是实验3

实验3的内存图如下,实参是函数ChangeByClass外的Person_ref对象,形参是函数ChangeByClass内的Person_ref对象。

捕获

从图中我们可以看出实参new出来之后就在托管堆上分配了内存,并且在栈上保存了对象的指针。

调用函数ChangeByClass后,由于没有ref参数,所以将栈上的实参p_val拷贝了一份作为形参,注意这里p_val(实参)p_val(形参)是指向托管堆上的同一地址。

所以说没有ref时,即使参数为引用类型(class)时,也可算是一种传值调用,这里的值就是托管堆中对象的地址(0x1000)。

调用函数ChangeByClass后,通过p_val(形参)修改了name属性的值,由于p_val(实参)p_val(形参)是指向托管堆上的同一地址,所以函数外的p_val(实参)的name属性也被修改了。

捕获

2.2 然后是实验5

上面的实验3从执行结果来看似乎是传引用调用,因为形参的改变导致了实参的改变。

下面的实验5就可以看出,p_val(形参)p_val(实参)并不是同一个变量,而是p_val(实参)的一个拷贝。

捕获

从图中可以看出第一步还是和实验3一样,但是在调用函数ChangeByClassNew后,就不一样了。

函数ChangeByClassNew中,对p_val(形参)重新分配了内存(new操作),使其指向了新的地址(0x1100),如下图:

捕获

所以p_val(形参)的name属性改了时候,p_val(实参)的name属性还是没变。

2.3 最后是实验6

我觉得实验6是真正的传引用调用。不废话了,直接上第一个图。

捕获

参数中加了ref关键字之后,其实传递的不是托管堆中对象的地址(0x1000),而是栈上p_val(实参)的地址(0x0001)。

所以这里实参和形参都是栈上的同一个东西,没有什么区别了。我觉得这才是真正的传引用调用。

然后调用了函数ChangeByClassRefNew,函数中对p_val(形参)重新分配了内存(new操作),使其指向了新的地址(0x1100)。

捕获

由于p_val(形参)就是p_val(实参),所以p_val(形参)的name属性改变后,函数ChangeByClassRefNew外的p_val(实参)的name属性也被改变了。

而原先分配的对象(地址0x1000)其实已经没有被引用了,随时会被GC回收。

3. 结论

  • 如果传递的参数是基元类型(int,float等)或结构体(struct),那么就是传值调用。
  • 如果传递的参数前有ref或者out关键字,那么就是传引用调用。
  • 如果传递的参数是类(class)并且没有ref或out关键字:
    1. 如果调用的函数中对参数重新进行了地址分配(new操作),那么执行结果类似传值调用
    2. 如果调用的函数中没有对参数重新进行了地址分配,直接就是使用了传递的参数,那么执行结果类似传引用调用

 

你可能感兴趣的:(Class,target,title,结构体,blank)