把引用对象作为参数传进一个方法,实际上是在栈上新分配了一块内存保存传入的地址。
如图所示,把局部变量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 。两者没有本质上的区别。