从一道面试题说说方法的引用传递和值传递
今天偶然在csdn论坛看到这么一篇帖子,就是说有这么一道面试题,题目如下:
using System;
public class Test1
{
public static void Main()
{
int num = 0;
Person p = new Person("Li");
A1(p, num);
Console.WriteLine("{0},{1}", p.name, num);
}
static void A1(Person p, int num)
{
p = new Person("Wang");
num = 1;
}
}
public class Person
{
public string name;
public Person(string name)
{
this.name = name;
}
}
using System;
public class Test1
{
public static void Main()
{
int num = 0;
Person p = new Person("Li");
A1(p, num);
Console.WriteLine("{0},{1}", p.name, num);
}
static void A1(Person p, int num)
{
p = new Person("Wang");
num = 1;
}
}
public class Person
{
public string name;
public Person(string name)
{
this.name = name;
}
}
说说上面的程序产生的结果,以及产生这个结果的原因是什么?
帖子上面说面试了十个人,居然有十个人答错了,貌似非常不正常啊,但是说白了,这里面主要就是想了解一下面试者对引用传递和值传递的理解;
我在以前过过一篇关于引用传递和值传递的博客,地址如下:http://www.zzzyk.com/kf/201201/116158.html
今天再就上面的面试题说一说,一方面巩固自己,另一方面方便心里没底的面试者彻底了解;
首先我们得清楚,在C#中,数据类型分为引用类型和值类型,值类型保存在堆栈中,引用类型稍微复杂点,引用类型分为两部分保存,引用类型的值保存在托管堆中,对该值的引用保存在堆栈中,值和值引用构成了一个完整的引用类型变量;我们经常使用下面的语法声明变量:
int i=0;
string str = "new string";
int i=0;
string str = "new string";
在i的声明过程中,系统做了两件事情,一件事情是在内存的堆栈中找到一个4字节的位置(int类型的长度为4字节),转换成代码应该这么表示:int i= new int();第二件事情是将0赋予i,转换成代码应该这么表示:i=0;综合下来实际代码应该如下所示:
int i=new int();
i=0;
int i=new int();
i=0;
在str的声明过程中,系统也是做了两件事情,只不过两件事情的手段不一致,第一件事情是在内存中找了两个地方,一个地方在托管堆中,用于存储str的值,另外一个地方在堆栈中,用于指向托管堆的存储位置;转换成代码也是一句话:string str=new string();第二件事也是将new sting这个字符串赋予变量值,但是new string是记录在托管堆中的,这是与值类型的区别,转换成代码应该这么表示:str=“new string”;综合下来代码是一样的,但是在分配内存时存在的差异就比较大了:
string str=new striing();
str="new string";
string str=new striing();
str="new string";
弄清楚了值类型和引用在内存中的保存方式,我们再来看看方法的参数传递,在C#中,所有的参数都是通过值来传递的,被调用的方法得到的都是该值的副本;这里一定要注意:被调用的方法得到的都是该值的副本,也就是说,我们看下面这个方法:
public void MethDouble(int i)
{
return i*2;
}
public void MethDouble(int i)
{
return i*2;
}
下面我们在程序中调用该方法,代码如下:
int i=3;
MethDouble(i);
在以上的代码中,系统做了如下事情:首先将变量在内存堆栈中copy一个副本,这个副本的变量名我们假设称为copy_i,得到副本后,再将副本copy_i传递给方法MethDouble执行;所以在以上的过程中,MethDouble方法内部所做的任何事情都不会对变量i产生任何影响;我们再来看看下面的方法:
<span style="white-space:pre"> </span>public void StrDouble(string str)
{
return str+str;
}
<span style="white-space:pre"> </span>public void StrDouble(string str)
{
return str+str;
}
下面我们在程序中调用该方法,代码如下:
string str = "myString";
StrDouble(str);
string str = "myString";
StrDouble(str);
同理,在以上的代码中,系统做了如下事情:首先将str在堆栈中的值引用变量copy一个副本,这个副本变量名我们假设称为copy_str,这个副本是一个引用,该引用指向的地址就是str引用指向的托管堆的地址,也就是托管堆中“myString”值,得到副本后,再将副本copy_str传递给方法执行,所以在操作过程中,方法对副本所做的修改都会直接修改托管堆中的值,从而影响方法外的str变量;
明白了以上的原理,我们再来看看开篇的面试题:
int num = 0;
Person p = new Person("Li");
int num = 0;
Person p = new Person("Li");声明了值类型变量num,引用类型变量p;
A1(p, num);
A1(p, num); 将num的副本和p的引用副本传递给方法;
这里我们可能有疑问了,p的引用传递进去了,那么对p所做的修改应该会影响托管堆中的值啊,我们这里看看方法A1方法的实现
static void A1(Person p, int num)
{
p = new Person("Wang");
num = 1;
}
static void A1(Person p, int num)
{
p = new Person("Wang");
num = 1;
} 注意了,上面的方法其实是用了一个障眼法,我把方法改改,大家再看看:
static void A1(Person person, int num)
{
person = new Person("Wang");
num = 1;
}
static void A1(Person person, int num)
{
person = new Person("Wang");
num = 1;
} 没改之前的方法里面一句p=new Person("wang");大家就以为p在堆栈中的值改变了,其实不然,p仅仅是一个堆栈中的副本,在方法A1中,是用了p=new Person("Wang"),此时系统做了两件事情:一件是在托管堆中找一个地方,用于存储p(实际上是下面方法的person)的值,然后将A1方法中的p引用指向该堆栈的位置,此时对A1方法的p所做的任何修改都是在修改第一件事情中找到的托管堆,因为A1方法中的引用指向更改了,所以主函数内的变量p此时指向的托管堆与A1方法内p指向的托管堆分别为不同的位置;所以此时A1方法所做的任何事情对主函数内的变量p都不会造成影响;
所以面试题的结果应该为:Li,0
int num = 0;
Person p = new Person("Li");
int num = 0;
补充:软件开发 , C# ,