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

王朝学院·作者佚名  2016-08-27
窄屏简体版  字體: 小  |  中  |  大  |  超大  

传值调用和传引用调用是几乎所有主流语言都会涉及到的问题,下面我谈谈我对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关键字:如果调用的函数中对参数重新进行了地址分配(new操作),那么执行结果类似传值调用如果调用的函数中没有对参数重新进行了地址分配,直接就是使用了传递的参数,那么执行结果类似传引用调用

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航