zoukankan      html  css  js  c++  java
  • C#

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

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


    接下来,简单分析一下这个结果: 
        (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

  • 相关阅读:
    阅读笔记03
    第十三周总结
    阅读笔记02
    第十二周总结
    第十一周总结
    阅读笔记01
    阅读笔记3
    第十一周总结
    阅读笔记
    HDFS-学习总结
  • 原文地址:https://www.cnblogs.com/gc2013/p/3829820.html
Copyright © 2011-2022 走看看