zoukankan      html  css  js  c++  java
  • 深刻理解C#的传值调用和传引用调用

    传值调用和传引用调用是几乎所有主流语言都会涉及到的问题,下面我谈谈我对C#中传值调用和传引用调用的理解。

    1. 一般对C#中传值调用和传引用调用的理解

    • 如果传递的参数是基元类型(int,float等)或结构体(struct),那么就是传值调用。
    • 如果传递的参数是类(class)那么就是传引用调用。
    • 如果传递的参数前有ref或者out关键字,那么就是传引用调用。

    验证示例的代码如下: 

    view sourceprint?

    01    using System;

    02   

    03    public class ArgsByRefOrValue

    04    {

    05        public static void Main(string[] args)

    06        {

    07            // 实验1. 传值调用--基元类型

    08            int i = 10;

    09            Console.WriteLine("before call ChangeByInt: i = " + i.ToString());

    10            ChangeByInt(i);

    11            Console.WriteLine("after call ChangeByInt: i = " + i.ToString());

    12   

    13            Console.WriteLine("==============================================");

    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   

    21            Console.WriteLine("==============================================");

    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   

    29            Console.WriteLine("==============================================");

    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   

    37            Console.ReadKey(true);

    38        }

    39   

    40        static void ChangeByInt(int i)

    41        {

    42            i = i + 10;

    43            Console.WriteLine("when calling ChangeByInt: i = " + i.ToString());

    44        }

    45   

    46        static void ChangeByStruct(Person_val p_val)

    47        {

    48            p_val.name = "new val name";

    49            Console.WriteLine("when calling ChangeByStruct: p_val.name = " + p_val.name);

    50        }

    51   

    52        static void ChangeByClass(Person_ref p_ref)

    53        {

    54            p_ref.name = "new ref name";

    55            Console.WriteLine("when calling ChangeByClass: p_ref.name = " + p_ref.name);

    56        }

    57   

    58        static void ChangeByClassRef(ref Person_ref p)

    59        {

    60            p.name = "new ref name";

    61            Console.WriteLine("when calling ChangeByClassRef: p.name = " + p.name);

    62        }

    63    }

    64   

    65    public struct Person_val

    66    {

    67        public string name;

    68    }

    69   

    70    public class Person_ref

    71    {

    72        public string name;

    73    }

     

    运行结果如下:

     

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

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

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

     

    001  using System;

    002 

    003  public class ArgsByRefOrValue

    004  {

    005      public static void Main(string[] args)

    006      {

    007          // 实验1. 传值调用--基元类型

    008          int i = 10;

    009          Console.WriteLine("before call ChangeByInt: i = " + i.ToString());

    010          ChangeByInt(i);

    011          Console.WriteLine("after call ChangeByInt: i = " + i.ToString());

    012 

    013          Console.WriteLine("==============================================");

    014          // 实验2. 传值调用--结构体

    015          Person_val p_val = new Person_val();

    016          p_val.name = "old val name";

    017          Console.WriteLine("before call ChangeByStruct: p_val.name = " + p_val.name);

    018          ChangeByStruct(p_val);

    019          Console.WriteLine("after call ChangeByStruct: p_val.name = " + p_val.name);

    020 

    021          Console.WriteLine("==============================================");

    022          // 实验3. 传引用调用--类

    023          Person_ref p_ref = new Person_ref();

    024          p_ref.name = "old ref name";

    025          Console.WriteLine("before call ChangeByClass: p_ref.name = " + p_ref.name);

    026          ChangeByClass(p_ref);

    027          Console.WriteLine("after call ChangeByClass: p_ref.name = " + p_ref.name);

    028 

    029          Console.WriteLine("==============================================");

    030          // 实验4. 传引用调用--利用ref

    031          Person_ref p = new Person_ref();

    032          p.name = "old ref name";

    033          Console.WriteLine("before call ChangeByClassRef: p.name = " + p.name);

    034          ChangeByClassRef(ref p);

    035          Console.WriteLine("after call ChangeByClassRef: p.name = " + p.name);

    036 

    037          Console.WriteLine("==============================================");

    038          // 实验5. 传引用调用--类 在调用的函数重新new一个对象

    039          Person_ref p_ref_new = new Person_ref();

    040          p_ref_new.name = "old new ref name";

    041          Console.WriteLine("before call ChangeByClassNew: p_ref_new.name = " + p_ref_new.name);

    042          ChangeByClassNew(p_ref_new);

    043          Console.WriteLine("after call ChangeByClassNew: p_ref_new.name = " + p_ref_new.name);

    044 

    045          Console.WriteLine("==============================================");

    046          // 实验6. 传引用调用--利用ref 在调用的函数重新new一个对象

    047          Person_ref p_new = new Person_ref();

    048          p_new.name = "old new ref name";

    049          Console.WriteLine("before call ChangeByClassRefNew: p_new.name = " + p_new.name);

    050          ChangeByClassRefNew(ref p_new);

    051          Console.WriteLine("after call ChangeByClassRefNew: p_new.name = " + p_new.name);

    052 

    053          Console.ReadKey(true);

    054      }

    055 

    056      static void ChangeByInt(int i)

    057      {

    058          i = i + 10;

    059          Console.WriteLine("when calling ChangeByInt: i = " + i.ToString());

    060      }

    061 

    062      static void ChangeByStruct(Person_val p_val)

    063      {

    064          p_val.name = "new val name";

    065          Console.WriteLine("when calling ChangeByStruct: p_val.name = " + p_val.name);

    066      }

    067 

    068      static void ChangeByClass(Person_ref p_ref)

    069      {

    070          p_ref.name = "new ref name";

    071          Console.WriteLine("when calling ChangeByClass: p_ref.name = " + p_ref.name);

    072      }

    073 

    074      static void ChangeByClassRef(ref Person_ref p)

    075      {

    076          p.name = "new ref name";

    077          Console.WriteLine("when calling ChangeByClassRef: p.name = " + p.name);

    078      }

    079 

    080      static void ChangeByClassNew(Person_ref p_ref_new)

    081      {

    082          p_ref_new = new Person_ref();

    083          p_ref_new.name = "new ref name";

    084          Console.WriteLine("when calling ChangeByClassNew: p_ref_new.name = " + p_ref_new.name);

    085      }

    086 

    087      static void ChangeByClassRefNew(ref Person_ref p_new)

    088      {

    089          p_new = new Person_ref();

    090          p_new.name = "new ref name";

    091          Console.WriteLine("when calling ChangeByClassRefNew: p_new.name = " + p_new.name);

    092      }

    093  }

    094 

    095  public struct Person_val

    096  {

    097      public string name;

    098  }

    099 

    100  public class Person_ref

    101  {

    102      public string name;

    103  }

     

    则运行结果为:

     

    实验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. 如果调用的函数中没有对参数重新进行了地址分配,直接就是使用了传递的参数,那么执行结果类似传引用调用
  • 相关阅读:
    MongoDB——(转)MongoDB 运维实战总结(转)
    MongoDB——4.4版本因果一致性会话
    架构——(转)用户中心 1亿数据 如何设计(转)
    MongoDB——命令备份
    JavaScript——JavaScript 弹窗
    git rebase
    shell脚本传参选项
    tftp 服务搭建
    gerrit搭建实现代码review
    Robot framework视频和教程分享
  • 原文地址:https://www.cnblogs.com/gc2013/p/4191534.html
Copyright © 2011-2022 走看看