好贷网好贷款

深刻理解C#的传值调用和传引用调用

发布时间:2016-12-5 18:38:04 编辑:www.fx114.net 分享查询网我要评论
本篇文章主要介绍了"深刻理解C#的传值调用和传引用调用",主要涉及到深刻理解C#的传值调用和传引用调用方面的内容,对于深刻理解C#的传值调用和传引用调用感兴趣的同学可以参考一下。

深刻理解C#的传值调用和传引用调用 Posted on 2011-05-18 23:11 wang_yb 阅读(5220) 评论(6) 编辑 收藏 传值调用和传引用调用是几乎所有主流语言都会涉及到的问题,下面我谈谈我对C#中传值调用和传引用调用的理解。 1. 一般对C#中传值调用和传引用调用的理解 如果传递的参数是基元类型(int,float等)或结构体(struct),那么就是传值调用。如果传递的参数是类(class)那么就是传引用调用。如果传递的参数前有ref或者out关键字,那么就是传引用调用。 验证示例的代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 using System;   public class ArgsByRefOrValue {     public static void Main(string[] args)     {         // 实验1. 传值调用--基元类型         int i = 10;         Console.WriteLine("before call ChangeByInt: i = " + i.ToString());         ChangeByInt(i);         Console.WriteLine("after call ChangeByInt: i = " + i.ToString());           Console.WriteLine("==============================================");         // 实验2. 传值调用--结构体         Person_val p_val = new Person_val();         p_val.name = "old val name";         Console.WriteLine("before call ChangeByStruct: p_val.name = " + p_val.name);         ChangeByStruct(p_val);         Console.WriteLine("after call ChangeByStruct: p_val.name = " + p_val.name);           Console.WriteLine("==============================================");         // 实验3. 传引用调用--类         Person_ref p_ref = new Person_ref();         p_ref.name = "old ref name";         Console.WriteLine("before call ChangeByClass: p_ref.name = " + p_ref.name);         ChangeByClass(p_ref);         Console.WriteLine("after call ChangeByClass: p_ref.name = " + p_ref.name);           Console.WriteLine("==============================================");         // 实验4. 传引用调用--利用ref         Person_ref p = new Person_ref();         p.name = "old ref name";         Console.WriteLine("before call ChangeByClassRef: p.name = " + p.name);         ChangeByClassRef(ref p);         Console.WriteLine("after call ChangeByClassRef: p.name = " + p.name);           Console.ReadKey(true);     }       static void ChangeByInt(int i)     {         i = i + 10;         Console.WriteLine("when calling ChangeByInt: i = " + i.ToString());     }       static void ChangeByStruct(Person_val p_val)     {         p_val.name = "new val name";         Console.WriteLine("when calling ChangeByStruct: p_val.name = " + p_val.name);     }       static void ChangeByClass(Person_ref p_ref)     {         p_ref.name = "new ref name";         Console.WriteLine("when calling ChangeByClass: p_ref.name = " + p_ref.name);     }       static void ChangeByClassRef(ref Person_ref p)     {         p.name = "new ref name";         Console.WriteLine("when calling ChangeByClassRef: p.name = " + p.name);     } }   public struct Person_val {     public string name; }   public class Person_ref {     public string name; } 运行结果如下: 看起来似乎上面代码中实验3和实验4是一样的,即对于类(class)来说,不管加不加ref或out,都是传引用调用。 其实,这只是表面的现象,只要稍微改一下代码,结果就不一样了。 修改上面代码,再增加两个实验。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 using System;   public class ArgsByRefOrValue {     public static void Main(string[] args)     {         // 实验1. 传值调用--基元类型         int i = 10;         Console.WriteLine("before call ChangeByInt: i = " + i.ToString());         ChangeByInt(i);         Console.WriteLine("after call ChangeByInt: i = " + i.ToString());           Console.WriteLine("==============================================");         // 实验2. 传值调用--结构体         Person_val p_val = new Person_val();         p_val.name = "old val name";         Console.WriteLine("before call ChangeByStruct: p_val.name = " + p_val.name);         ChangeByStruct(p_val);         Console.WriteLine("after call ChangeByStruct: p_val.name = " + p_val.name);           Console.WriteLine("==============================================");         // 实验3. 传引用调用--类         Person_ref p_ref = new Person_ref();         p_ref.name = "old ref name";         Console.WriteLine("before call ChangeByClass: p_ref.name = " + p_ref.name);         ChangeByClass(p_ref);         Console.WriteLine("after call ChangeByClass: p_ref.name = " + p_ref.name);           Console.WriteLine("==============================================");         // 实验4. 传引用调用--利用ref         Person_ref p = new Person_ref();         p.name = "old ref name";         Console.WriteLine("before call ChangeByClassRef: p.name = " + p.name);         ChangeByClassRef(ref p);         Console.WriteLine("after call ChangeByClassRef: p.name = " + p.name);           Console.WriteLine("==============================================");         // 实验5. 传引用调用--类 在调用的函数重新new一个对象         Person_ref p_ref_new = new Person_ref();         p_ref_new.name = "old new ref name";         Console.WriteLine("before call ChangeByClassNew: p_ref_new.name = " + p_ref_new.name);         ChangeByClassNew(p_ref_new);         Console.WriteLine("after call ChangeByClassNew: p_ref_new.name = " + p_ref_new.name);           Console.WriteLine("==============================================");         // 实验6. 传引用调用--利用ref 在调用的函数重新new一个对象         Person_ref p_new = new Person_ref();         p_new.name = "old new ref name";         Console.WriteLine("before call ChangeByClassRefNew: p_new.name = " + p_new.name);         ChangeByClassRefNew(ref p_new);         Console.WriteLine("after call ChangeByClassRefNew: p_new.name = " + p_new.name);           Console.ReadKey(true);     }       static void ChangeByInt(int i)     {         i = i + 10;         Console.WriteLine("when calling ChangeByInt: i = " + i.ToString());     }       static void ChangeByStruct(Person_val p_val)     {         p_val.name = "new val name";         Console.WriteLine("when calling ChangeByStruct: p_val.name = " + p_val.name);     }       static void ChangeByClass(Person_ref p_ref)     {         p_ref.name = "new ref name";         Console.WriteLine("when calling ChangeByClass: p_ref.name = " + p_ref.name);     }       static void ChangeByClassRef(ref Person_ref p)     {         p.name = "new ref name";         Console.WriteLine("when calling ChangeByClassRef: p.name = " + p.name);     }       static void ChangeByClassNew(Person_ref p_ref_new)     {         p_ref_new = new Person_ref();         p_ref_new.name = "new ref name";         Console.WriteLine("when calling ChangeByClassNew: p_ref_new.name = " + p_ref_new.name);     }       static void ChangeByClassRefNew(ref Person_ref p_new)     {         p_new = new Person_ref();         p_new.name = "new ref name";         Console.WriteLine("when calling ChangeByClassRefNew: p_new.name = " + p_new.name);     } }   public struct Person_val {     public string name; }   public class Person_ref {     public string name; } 则运行结果为: 实验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关键字: 如果调用的函数中对参数重新进行了地址分配(new操作),那么执行结果类似传值调用如果调用的函数中没有对参数重新进行了地址分配,直接就是使用了传递的参数,那么执行结果类似传引用调用

上一篇:ios版本更新
下一篇:USB device hot plug detection

相关文章

相关评论