[C#基础]理解方法的参数传递

来源:互联网 发布:知秋歌词 编辑:程序博客网 时间:2024/04/27 03:04

前几天看到论坛里有人不理解方法传递带Ref的参数与不带Ref的参数的区别,所以动手写了这篇文章。

先看下面的代码:

using System;
using System.Collections.Generic;
using System.Text;

namespace test
{
    
class Program
    
{
        
static void Main(string[] args)
        
{
            TestClass t 
= new TestClass();
            t.Test();
        }

    }


    
class TestClass
    
{
        
public void Test()
        
{
            
int i = 1;
            StringBuilder sb 
= new StringBuilder("InitString");
            
string returnvalue = Method1(i, sb);
            Console.Write(
string.Format("i = {0}, sb = {1}, returnvalue = {2}", i, sb, returnvalue));
            Console.Read();
        }


        
string Method1(int intvalue, StringBuilder objectvalue)
        
{
            intvalue 
= 2;
            objectvalue.Append(
"AppendString");
            objectvalue 
= new StringBuilder("OtherString");
            
return objectvalue.ToString();
        }

    }

}

 在Test()方法中当执行到:
string returnvalue = Method1(i, sb);
这一句时,系统首先分配二个变量intvalue和objectvalue的地址(变量地址),然后读取传递过来i和sb变量的值。
1)i
因为i是值类型,所以i变量的值地址是指向堆栈上的一个地址,堆栈上的这一个地址保存的是一个直接数值。
所以读取i时,取得到的是堆栈地址上的具体数值。假设i的值地址0x0020,在堆栈地址0x0020上保存的是一个数值1。
系统为intvalue的值地址分配一个地址值如0x0040,并在堆栈地址0x0040上写入数值1。

2)sb
sb是一个引用类型,所以它的值地址是托管堆上的一个地址,这个堆地址存放的是包括实际内容的指针,
即指向实际内容的地址。所以读取sb时根据值地址,它从堆地址中读出的仍是一个内存地址。
假设sb的值地址是0x1020,在堆地址0x1020处保存是一个地址0x2020
系统为objectvalue值地址分配一个地址值如0x1040,并把地址值0x2020写入堆地址0x1040上。
所以sb和objectvalue最终都是指向地址0x2020,也就是说sb和objectvalue指向同一个对象。
所以当在方法内部使用objectvalue改变对象的内容后:
objectvalue.Append("AppendString");
sb的值也会随之变化,变为InitStringAppendString,就是由于它们最终指向的都是同一个对象
由于C#不使用指针,所以当系统读到值是地址时,会继续根据地址读取其内容,直到读取的内容不是地址为止。

可以看出方法从调用一开始,就已经与传入参数没有任何关系了。

当执行到objectvalue = new StringBuilder("OtherString");这一句时,系统分配一个新地址,如
0x8040,然后写到objectvalue值地址0x1040的内容中。

最后地址分配如下表

变量
值地址
i
0x0020
1
intvalue
0x0040
2
sb
0x1020
0x2020
objectvalue
0x1040
(new之前)0x2020
(new之后)0x8040 
//再new一下又是另一个地址

再说一下string类型,string是一个比较特殊的引用类型,当它从一个值改为另一个值时,总是最新分配内存。
string s = "1st";
s = "2nd";

执行s = "2nd";时相当于执行 s = new string("2nd");由于这个原因,string类型看上去很像值类型。

再看下一个例子:

using System;
using System.Collections.Generic;
using System.Text;

namespace test
{
    
class Program
    
{
        
static void Main(string[] args)
        
{
            TestClass t 
= new TestClass();
            t.Test();
        }

    }


    
class TestClass
    
{
        
public void Test()
        
{
            
int i = 1;
            StringBuilder sb 
= new StringBuilder("InitString");
            
string returnvalue = Method1(out i, ref sb);
            Console.Write(
string.Format("i = {0}, sb = {1}, returnvalue = {2}", i, sb, returnvalue));
            Console.Read();
        }


        
string Method1(out int intvalue, ref StringBuilder objectvalue)
        
{
            intvalue 
= 2;
            objectvalue.Append(
"AppendString");
            objectvalue 
= new StringBuilder("OtherString");
            
return objectvalue.ToString();
        }

    }

}

当执行int i = 1;时,系统会为i变量分配一个地址(变量地址)如0x1200, 然后为其值分配一个地址0x0020,
0x0020是分配在堆栈的一个地址,该地址的值为1
当执行StringBuilder sb = new StringBuilder("InitString");时,系统会为sb变量分配一个地址(变量地址)
如0x1220,然后为其值分配一个地址0x0040,然后创建StringBuilder类的实例并初始化,最后把这个实例内容
的地址0x2020写到地址0x0040的内容中。

调用Method1(out i, ref sb);时
1)out i
out表明它的值地址指向一个变量地址,
intvalue的值地址写入i的变量地址0x1200,
由于是out类型,所以还把i的值地址设为未初始化,即将0x0020改为0x0000(假设空地址void*为0x0000)。
执行intvalue = 2;时,在堆栈上分配一个地址0x0060再写入值2, i的值地址设为0x0060。

2)ref cb
ref与out一样,它的值地址也是指向一个变量地址
objectvalue的值地址上写入sb的变量地址0x1220
执行objectvalue.Append("AppendString");时,找到sb值地址0x0040指向的地址0x2020的对象进行操作,
这时objectvalue就可以看作sb变量了,下面objectvalue = new StringBuilder("OtherString");看作
cb = new StringBuilder("OtherString");就行了。

最后地址分配如下表

变量
变量地址
值地址
i
0x1200
(调用前)0x0020
1
(调用后)0x0000
不可知
(赋值后)0x0060
2
intvalue
不重要
0x1200
 
sb
0x1220
0x0040
(new之前)0x2020
(new之后)0x8040
objectvalue
不重要
0x1220
 

可见不用ref/out时,变动的是变量intvalue和objectvalue(参数变量)
使用ref/out后,变动的是变量i和cb(传入变量)

上面是我对方法的参数传递的理解,若有什么问题请指出。

原创粉丝点击