把引用对象作为参数传进一个方法,实际上是在栈上新分配了一块内存保存传入的地址。

 

如图所示,把局部变量name传入M2方法后,栈上新开了一块内存S,用来保存"joe"的地址。

其实拿string来说明不太合适,因为string类是不可变的。但是为借用CLR VIA C#中现成的图咱们就将就一下了。

这时如果能够直接修改s所指向的内容的话(在C#中string是不可变类,无法演示),外部的局部变量name由于和形参s指向的是同一个对象,因此name也会改变。(图1)

但是如果把s指向一个新的的string时,比如:

void M2(string s){

  s = "new string";

}

那么这时再对s改变,name的内容不会改变。因为两个变量分别指向了堆上的不同string (图2)

用图来说明就是:

图一:刚进入M2时

 

 

 

 

图二:给s赋值后

 

如果用上ref,在这种情况下,编译器将不会为形参s在栈上新分配一块内存,编译器将使用由调用者所指定的那个引用地址,按上图来说是使用name的地址。

图三:

在C中,为了解决这种栈上的变量复制问题,我们可以采用指向指针的指针,如 char **s 。

这时再对s修改,指向一个新的对象,外部的name也会改变。

 

由于用string的话,无法演示直接修改的情况,因为改变string实际上也是生成了新的string。因此我使用student类来演示。

代码示例如下:

 class Student
{
  public string name = "a";
}

  class
Program
{
static void Main(string[] args)
{
var s1 = new Student();
var s2 = new Student();
       var
s3 = new Student();

ModifyWithRef(ref s1);
ModifyWithoutRef(s2);
       DirectlyModifyWithoutRef(s3);

       Console.WriteLine("Directly modify without ref: "+s3.name); //结果被直接修改为changed,两个不同变量指向同一个对象。对应图1
       Console.WriteLine("without ref: "+s2.name); //结果仍为a,对应图2
       Console.WriteLine("with ref: "+s1.name); //结果为changed,因为s1指向了在方法内新创建的对象。

Console.ReadLine();
}

static void ModifyWithRef(ref Student s)
{
s = new Student();
s.name = "changed";
}

static void ModifyWithoutRef(Student s)
{
s = new Student();
s.name = "changed";
}
    
        static void DirectlyModifyWithoutRef(Student s)
{
s.name = "changed";
}

}




简而言之,按值传递不是值参数是值类型,而是指形参变量会复制实参变量,也就是会在栈上多创建一个相同的变量。而按引用传递则不会。
在C#中可以通过ref 和 out来决定参数是否按照引用传递。如果方法内部不需要用到原来的值,就用out, 如果还需要用原来的值做一些判断之类的事情,则用ref 。两者没有本质上的区别。

posted on 2012-03-11 21:43  一路转圈的雪人  阅读(1849)  评论(2编辑  收藏  举报