C# - 函数参数的传递

近段时间,有几个刚刚开始学习C#语言的爱好者问我:C#中的函数,其参数的传递,按值传递和按引用传递有什么区别。针对这一问题,我简单写了个示例程序,用以讲解,希望我没有把他们绕晕。因为,常听别人说起:“你不说我还明白,你一说,我就糊涂了”。 
    好,现在开始吧。 
    我们知道,在C#中,类型有值类型(例如int)和引用类型(例如string)之分,传递参数有按值传递和按引用传递之分。这样,简单的组合一下,我们可以得到以下几种传递方式:(1)按值传递值类型。(2)按值传递引用类型。(3)按引用传递值类型。(4)按引用传递引用类型。一般来说,除非使用特定的关键字(ref和out)否则参数是按值传递的。也就是说,会传递一个副本。传递副本的一个好处是,可以避免误操作而影响了原始值。原因是在被调用的函数体内,操作的是副本的值,而不是原始值。当然,传递副本也是有副作用的,最为突出的应该是由于复制而产生的性能损耗,这点在大型的值类型身上尤为突出。那么C#的编译器的默认行为为什么不是使用按引用传递参数呢?呵呵,其实我没仔细深入思考过这个问题。我猜测,是因为安全因素吧,就是怕函数误操作了原始值。这点应该和C#的编译器要求显示使用关键字(ref和out)差不多,都是为了清楚地表达使用的意图,以避免误操作。使用ref等关键字,暗示函数调用者知道,在函数体内,也许存在修改原始值的语句,会改变参数的值(或者叫状态)。 
    用个简单的示例演示一下。 
    示例代码如下所示:

Java代码  

  1. using System;
  2. namespace DonLiang
  3. {
  4. class Sample
  5. {
  6. 值类型测试函数#region 值类型测试函数
  7. public static void foo(int x)
  8. {
  9. x = 10;
  10. }
  11. //*
  12. public static void foo(ref int x)
  13. {
  14. x = 10;
  15. }
  16. //*/
  17. /**//*
  18. public static void foo(out int x)
  19. {
  20. x = 10;
  21. }
  22. //*/
  23. #endregion
  24. 辅助引用类型#region 辅助引用类型
  25. public class Point
  26. {
  27. private int m_x;
  28. private int m_y;
  29. public Point()
  30. {
  31. m_x = 0;
  32. m_y = 0;
  33. }
  34. public Point(int x, int y)
  35. {
  36. m_x = x;
  37. m_y = y;
  38. }
  39. public void Change(int x, int y)
  40. {
  41. m_x = x;
  42. m_y = y;
  43. }
  44. public override string ToString()
  45. {
  46. return string.Format("The Point is ({0},{1})", m_x.ToString(), m_y.ToString());
  47. }
  48. }
  49. #endregion
  50. 引用类型测试函数#region 引用类型测试函数
  51. public static void foo(Point p)
  52. {
  53. p.Change(10, 10);
  54. }
  55. public static void foo(ref Point p)
  56. {
  57. p.Change(100, 100);
  58. }
  59. public static void other(Point p)
  60. {
  61. Point tmp = new Point(13, 16);
  62. p = tmp;
  63. }
  64. public static void other(ref Point p)
  65. {
  66. Point tmp = new Point(138, 168);
  67. p = tmp;
  68. }
  69. #endregion
  70. Main#region Main
  71. static void Main(string[] args)
  72. {
  73. int n = 5;
  74. //call the foo(int x) method and check what happened.
  75. Console.WriteLine("before call foo(int x) the n = " + n.ToString());
  76. foo(n);
  77. Console.WriteLine("after call foo(int x) the n = " + n.ToString());
  78. Console.WriteLine("--------------------------------------------------------------");
  79. //call the foo(ref int x) method and check what happened.
  80. Console.WriteLine("before call foo(ref int x) the n = " + n.ToString());
  81. foo(ref n);
  82. //foo(out n);
  83. Console.WriteLine("after call foo(ref int x) the n = " + n.ToString());
  84. Console.WriteLine("--------------------------------------------------------------");
  85. Point p = new Point(5, 5);
  86. Point q = p;
  87. //call the foo(Point p) method and check what happened.
  88. Console.WriteLine("before call foo(Point p) the p = " + p.ToString());
  89. foo(p);
  90. Console.WriteLine("after call foo(Point p) the p = " + p.ToString());
  91. Console.WriteLine("q = " + q.ToString());
  92. Console.WriteLine("--------------------------------------------------------------");
  93. //call the foo(ref Point p) method and check what happened.
  94. Console.WriteLine("before call foo(ref Point p) the n = " + p.ToString());
  95. foo(ref p);
  96. Console.WriteLine("after call foo(ref Point p) the n = " + p.ToString());
  97. Console.WriteLine("q = " + q.ToString());
  98. Console.WriteLine("--------------------------------------------------------------");
  99. //call the other(Point p) method and check what happened.
  100. Console.WriteLine("before call other(Point p) the n = " + p.ToString());
  101. other(p);
  102. Console.WriteLine("after call other(Point p) the n = " + p.ToString());
  103. Console.WriteLine("q = " + q.ToString());
  104. Console.WriteLine("--------------------------------------------------------------");
  105. //call the other(ref Point p) method and check what happened.
  106. Console.WriteLine("before call other(ref Point p) the n = " + p.ToString());
  107. other(ref p);
  108. Console.WriteLine("after call other(ref Point p) the n = " + p.ToString());
  109. Console.WriteLine("q = " + q.ToString());
  110. Console.ReadLine();
  111. }
  112. #endregion
  113. }
  114. }

接下来,简单分析一下这个结果: 
    (1)按值传递值类型 
    初始值为5,调用函数的时候,弄了个副本给函数折腾,于是,从函数返回后,值还是5。嗯,本来应该弄个堆栈图出来的,可是,图是在太难画,因此,我偷懒,用VS2008的调试监视器看看: 
    
    (2)按引用传递值类型 
    初始值还是5,这次换了个传递参数的方式——按引用传递,这次可不是副本了,而是原始值(的地址),这就类似大牌的武打演员总不能老是使用替身一样,偶尔还是要亲自上阵的。既然是亲自上阵,那么,值被修改为10就不足为奇了。正如结果图所示,n=10了。 
    
    (3)按值传递引用类型 和 按引用传递引用类型 
    之所以把这两个放在一起讲,是因为,如结果图所示,两种传递方式,都成功修改了值——这两个函数都分别调用了一个辅助修改的函数Change,去修改内部状态,即m_x,m_y的值,从5到10。呃,竟然都可以成功修改原始值,那么,为什么会存在两种方式呢?它们有什么区别吗?分别用在什么地方?为了说明他们的区别,我特意写了两个名为other的函数,在函数内new一个Point对象,并使从参数传递过来的引用这个新生成的Point对象。值得提醒的是,这个引用其定义在函数体外。其运行如上图我用方框框起来那个。 
    可以很清楚地看到,通过值传递方式,可以改变其值,却不能改变其本身所引用的对象;而按引用传递方式可以。

顺便提一下,代码中,有一段注释掉的代码,使用out关键字的。当你尝试将其两者一起写着,然后,编译,C#编译器是会提示错误的(error CS0663: ‘foo‘ cannot define overloaded methods that differ only on ref and out)。其原因是,C#编译器,对ref和out生成的IL代码,是相同的;而在CLR层面,是没有ref和out的区别的。C#中,ref和out的区别,主要是,谁负责初始化这个参数使之能用——ref形式是函数外初始化,而out是函数内初始化。 
转自:http://www.cnblogs.com/DonLiang/archive/2008/02/16/1070717.html

C# - 函数参数的传递,布布扣,bubuko.com

时间: 2024-12-14 18:18:14

C# - 函数参数的传递的相关文章

react 函数参数的传递

js中函数参数的传递形式较为简单 function show(s){ console.log(s); } show(''hello);  就会输出hello. 在react JSX中函数参数的传递需要用到 bind 这里有个例子可以作为参考 http://www.jianshu.com/p/d745514e547b 需要注意的是得先定义this然后再使用,直接使用this会报错,函数为定义 需要通过bind方法来绑定参数,第一个参数指向this,第二个参数开始才是事件函数接收到的参数 下面这个例

(转载)你好,C++(26)如何与函数内部进行数据交换?5.1.3 函数参数的传递

你好,C++(26)如何与函数内部进行数据交换?5.1.3 函数参数的传递 5.1.3  函数参数的传递 我们知道,函数是用来完成某个功能的相对独立的一段代码.函数在完成这个功能的时候,往往需要外部数据的支持,这时就需要在调用这个函数时向它传递所需要的数据它才能完成这个功能获得结果.例如,当调用一个加法函数时,需要向它传递两个数作为加数和被加数,然后在它内部才能对这两个数进行计算获得加和结果.在定义一个函数的时候,如果这个函数需要跟外部进行数据交换,就需要在函数定义中加入形式参数表,以确定函数的

转:python 函数参数的传递(参数带星号的说明)

python中函数参数的传递是通过赋值来传递的.函数参数的使用又有俩个方面值得注意:1.函数参数是如何定义的 2.在调用函数的过程中参数是如何被解析 先看第一个问题,在python中函数参数的定义主要有四种方式:1.F(arg1,arg2,...)这 是最常见的定义方式,一个函数可以定义任意个参数,每个参数间用逗号分割,用这种方式定义的函数在调用的的时候也必须在函数名后的小括号里提供个数相等的 值(实际参数),而且顺序必须相同,也就是说在这种调用方式中,形参和实参的个数必须一致,而且必须一一对应

C语言入门(十三)函数参数的传递和值返回

 函数参数的传递和值返回  前言: 前面我们说的都是无参数无返回值的函数,实际程序中,我们经常使用到带参数有返回值的函数. 一.函数参数传递 1.形式参数和实际参数 函数的调用值把一些表达式作为参数传递给函数.函数定义中的参数是形式参数,函数的调用者提供给函数的参数叫实际参数.在函数调用之前,实际参数的值将被拷贝到这些形式参数中. 2.参数传递 先看一个例子: void a(int); /*注意函数声明的形式*/ main() { int num; scanf(%d,&num); a(num);

python 函数参数的传递(参数带星号的说明) 元组传递 字典传递

python 函数参数的传递(参数带星号的说明) 元组传递 字典传递 *arg 代表的是arg元祖,**kwd代表的是kwd名称的字典. 那函数传参数或是使用参数的时候,什么时候带*号什么时候不带*号呢?我这点总是理解不上来,或者说有点混乱.参考下面几个小函数,来理解下 >>> def a(*x): print (x) >>> def b(x): print(x) >>> def c(*x): print(*x) >>> x = (1

Java中,函数参数的传递,是值传递还是引用传递

当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?  答:是值传递. 可以这么理解:有一个Person(name="S"),作为参数传给一个方法,在此方法中将此Person重新设置name=“B”,那么有一下结论 ①这个Person还是这个Person,一直都是他,但是他的名字被改了,不管是原来的引用还是方法里的引用,引用的都是一个对象,所以,所有引用的值都变了 ②如果在方法中重新new了一个Person赋给参数,

python 函数参数多种传递方法

python进阶教程之函数参数的多种传递方法 我们已经接触过函数(function)的参数(arguments)传递.当时我们根据位置,传递对应的参数.我们将接触更多的参数传递方式.回忆一下位置传递:复制代码代码如下: def f(a,b,c): return a+b+cprint(f(1,2,3)) 在调用f时,1,2,3根据位置分别传递给了a,b,c.关键字传递有些情况下,用位置传递会感觉比较死板.关键字(keyword)传递是根据每个参数的名字传递参数.关键字并不用遵守位置的对应关系.依然

python下函数参数的传递(*和**)

1.F(arg1,arg2,...) 这 是最常见的定义方式,一个函数可以定义任意个参数,每个参数间用逗号分割,用这种方式定义的函数在调用的的时候也必须在函数名后的小括号里提供个数相等的 值(实际参数),而且顺序必须相同,也就是说在这种调用方式中,形参和实参的个数必须一致,而且必须一一对应,也就是说第一个形参对应这第一个实参.例 如: def a(x,y): print x,y 调用该函数,a(1,2)则x取1,y取2,形参与实参相对应,如果a(1)或者a(1,2,3)则会报错. 2.F(arg

怎样理解函数参数的传递

前言: 基本类型(数值 / 字符串 / 布尔值)和引用类型(对象)作为参数传递给函数时的行为是不一样的. 基本类型传递的是值的地址, 它在函数内部的改变不会影响到原变量. 引用类型传递的是引用的地址, 它在函数内部的改变实际上是对原变量的改变. 第一步: 下面演示基本类型的值做实参时的情况 function change(bol){ bol = !bol; console.log(bol) } var a = true; change(a); // false console.log(a); /