zoukankan      html  css  js  c++  java
  • [你必须知道的.NET]第十二回:参数之惑传递的艺术(下)

    本文将介绍以下内容:

    • 按值传递与按引用传递深论
    • ref和out比较 
    • 参数应用浅析 

     

    接上篇继续,『第十一回:参数之惑---传递的艺术(上)

    4.2 引用类型参数的按值传递

    当传递的参数为引用类型时,传递和操作的是指向对象的引用,这意味着方法操作可以改变原来的对象,但是值得思考的是该引用或者说指针本身还是按值传递的。因此,我们在此必须清楚的了解以下两个最根本的问题:

    • 引用类型参数的按值传递和按引用传递的区别?
    • string类型作为特殊的引用类型,在按值传递时表现的特殊性又如何解释?

    首先,我们从基本的理解入手来了解引用类型参数按值传递的本质所在,简单的说对象作为参数传递时,执行的是对对象地址的拷贝,操作的是该拷贝地址。这在本质上和值类型参数按值传递是相同的,都是按值传递。不同的是值类型的“值”为类型实例,而引用类型的“值”为引用地址。因此,如果参数为引用类型时,在调用方代码中,可以改变引用的指向, 从而使得原对象的指向发生改变,如例所示: 

    //引用类型参数的按值传递
    using System;
    namespace My_Must_net
    {
        class Args
        {
            public static void Main()
            {
                ArgsByRef abf = new ArgsByRef();
                AddRef(abf);
                Console.WriteLine(abf.i);
            }
    
            private static void AddRef(ArgsByRef abf)
            {
                abf.i = 20;
                Console.WriteLine(abf.i);
            }
        }
    
        class ArgsByRef
        {
            public int i = 10;
        }
    }

    因此,我们进一步可以总结为:按值传递的实质的是传递值,不同的是这个值在值类型和引用类型的表现是不同的:参数为值类型时,“值”为实例本身,因此传递的是实例拷贝,不会对原来的实例产生影响;参数为引用类型时,“值”为对象引用,因此传递的是引用地址拷贝,会改变原来对象的引用指向,这是二者在统一概念上的表现区别,理解了本质也就抓住了根源。关于值类型和引用类型的概念可以参考《第八回:品味类型---值类型与引用类型(上)-内存有理》《第九回:品味类型---值类型与引用类型(中)-规则无边》《第十回:品味类型---值类型与引用类型(下)-应用征途》,相信可以通过对系列中的值类型与引用类型的3篇的理解,加深对参数传递之惑的昭雪。

    了解了引用类型参数按值传递的实质,我们有必要再引入另一个参数传递的概念,那就是:按引用传递,通常称为引用参数。这二者的本质区别可以小结为:

    • 引用类型参数的按值传递,传递的是参数本身的值,也就是上面提到的对象的引用;
    • 按引用传递,传递的不是参数本身的值,而是参数的地址。如果参数为值类型,则传递的是该值类型的地址;如果参数为引用类型,则传递的是对象引用的地址。

    关于引用参数的详细概念,我们马上就展开来讨论,不过还是先分析一下string类型的特殊性,究竟特殊在哪里?

    关于string的讨论,在本人拙作《第九回:品味类型---值类型与引用类型(中)-规则无边》已经有了讨论,也就是开篇陈述的本文成文的历史,所以在上述分析的基础上,我认为应该更能对第九回的问题,做以更正。

    string本身为引用类型,因此从本文的分析中可知,对于形如

    static void ShowInfo(string aStr){...}

    的传递形式,可以清楚的知道这是按值传递,也就是本文总结的引用类型参数的按值传递。因此,传递的是aStr对象的值,也就是aStr引用指针。接下来我们看看下面的示例来分析,为什么string类型在传递时表现出特殊性及其产生的原因?

    using System;
    
    namespace My_Must_net
    {
        class how2str
        {
            static void Main()
            {
                string str = "Old String";
                ChangeStr(str);
                Console.WriteLine(str);
            }
    
            static void ChangeStr(string aStr)
            {
                aStr = "Changing String";
                Console.WriteLine(aStr);
            }
        }
    }

    下面对上述示例的执行过程简要分析一下:首先,string str = "Old String"产生了一个新的string对象,如图表示:

     

    然后执行ChangeStr(aStr),也就是进行引用类型参数的按值传递,我们强调说这里传递的是引用类型的引用值,也就是地址指针;然后调用ChangeStr方法,过程aStr = "Changing String"完成了以下的操作,先在新的一个地址生成一个string对象,该新对象的值为"Changing String",引用地址为0x06赋给参数aStr,因此会改变aStr的指向,但是并没有改变原来方法外str的引用地址,执行过程可以表示为:

     

    因此执行结果就可想而知,我们从分析过程就可以发现string作为引用类型,在按值传递过程中和其他引用类型是一样的。如果需要完成ChangeStr()调用后,改变原来str的值,就必须使用ref或者out修饰符,按照按引用传递的方式来进行就可以了,届时aStr = "Changing String"改变的是str的引用,也就改变了str的指向,具体的分析希望大家通过接下来的按引用传递的揭密之后,可以自行分析。

    4.3 按引用传递之ref和out

    不管是值类型还是引用类型,按引用传递必须以ref或者out关键字来修饰,其规则是:

    • 方法定义和方法调用必须同时显示的使用ref或者out,否则将导致编译错误;
    • CRL允许通过out或者ref参数来重载方法,例如: 
    using System;
    namespace My_Must_net._11_Args
    {
        class TestRefAndOut
        {
            static void ShowInfo(string str)
            {
                Console.WriteLine(str);
            }
            static void ShowInfo(ref string str)
            {
                Console.WriteLine(str);
            }
        }
    }

    当然,按引用传递时,不管参数是值类型还是引用类型,在本质上也是相同的,这就是:ref和out关键字将告诉编译器,方法传递的是参数地址,而不是参数本身。理解了这一点也就抓住了按引用传递的本质,因此根据这一本质结论我们可以得出以下更明白的说法,这就是:

    • 不管参数本身是值类型还是引用类型,按引用传递时,传递的是参数的地址,也就是实例的指针。
    • 如果参数是值类型,则按引用传递时,传递的是值类型变量的引用,因此在效果上类似于引用类型参数的按值传递方式,其实质可以分析为:值类型的按引用传递方式,实现的是对值类型参数实例的直接操作,方法调用方为该实例分配内存,而被调用方法操作该内存,也就是值类型的地址;而引用类型参数的按值传递方式,实现的是对引用类型的“值”引用指针的操作。例如:  
    using System;
    
    namespace Anytao.net.My_Must_net
    {
        class TestArgs
        {
            static void Main(string[] args)
            {
                int i = 100;
                string str = "One";
                ChangeByValue(ref i);
                ChangeByRef(ref str);
                Console.WriteLine(i);
                Console.WriteLine(str);
            }
    
            static void ChangeByValue(ref int iVlaue)
            {
                iVlaue = 200;
            }
    
            static void ChangeByRef(ref string sValue)
            {
                sValue = "One more.";
            }
        }
    }

    如果参数是引用类型,则按引用传递时,传递的是引用的引用而不是引用本身,类似于指针的指针概念。示例只需将上述string传递示例中的ChangeStr加上ref修饰即可。 

    下面我们再进一步对ref和out的区别做以交代,就基本阐述清楚了按引用传递的精要所在,可以总结为:

    • 相同点:从CRL角度来说,ref和out都是指示编译器传递实例指针,在表现行为上是相同的。最能证明的示例是,CRL允许通过ref和out来实现方法重载,但是又不允许通过区分ref和out来实现方法重载,因此从编译角度来看,不管是ref还是out,编译之后的代码是完全相同的。例如:
    using System;
    namespace Anytao.net.My_Must_net._11_Args
    {
        class TestRefAndOut
        {
            static void ShowInfo(string str)
            {
                Console.WriteLine(str);
            }
            static void ShowInfo(ref string str)
            {
                Console.WriteLine(str);
            }
            static void ShowInfo(out string str)
            {
                str = "Hello, anytao.";
                Console.WriteLine(str);
            }
        }
    }

    编译器将提示: “ShowInfo”不能定义仅在 ref 和 out 上有差别的重载方法。 

    • 不同点:使用的机制不同。ref要求传递之前的参数必须首先显示初始化,而out不需要。也就是说,使用ref的参数必须是一个实际的对象,而不能指向null;而使用out的参数可以接受指向null的对象,然后在调用方法内部必须完成对象的实体化。  

    5. 结论

    完成了对值类型与引用类型的论述,在这些知识积累的基础上,本文期望通过深入的论述来进一步的分享参数传递的艺术,解开层层疑惑的面纱。从探讨问题的角度来说,参数传递的种种误区其实根植与对值类型和引用类型的本质理解上,因此完成了对类型问题的探讨再进入参数传递的迷宫,我们才会更加游刃有余。我想,这种探讨问题的方式,也正是我们追逐问题的方式,深入进入.NET的高级殿堂是绕不开这一选择的。

  • 相关阅读:
    【转+补充】在OpenCV for Android 2.4.5中使用SURF(nonfree module)
    Delphi StarOffice Framework Beta 1.0 发布
    Angular ngIf相关问题
    angularjs文档下载
    公众号微信支付开发
    公众号第三方平台开发 教程六 代公众号使用JS SDK说明
    公众号第三方平台开发 教程五 代公众号处理消息和事件
    公众号第三方平台开发 教程四 代公众号发起网页授权说明
    公众号第三方平台开发 教程三 微信公众号授权第三方平台
    公众号第三方平台开发 教程二 component_verify_ticket和accessToken的获取
  • 原文地址:https://www.cnblogs.com/ShaYeBlog/p/2707980.html
Copyright © 2011-2022 走看看