引用传递和值传递的区别

有这么一道面试题,题目如下:

  1. using System;  
  2. public class Test1  
  3. {  
  4.     public static void Main()  
  5.     {  
  6.         int num = 0;  
  7.         Person p = new Person("Li");  
  8.         A1(p, num);  
  9.         Console.WriteLine("{0},{1}", p.name, num);  
  10.     }  
  11.     static void A1(Person p, int num)  
  12.     {  
  13.         p = new Person("Wang");  
  14.         num = 1;  
  15.     }  
  16. }  
  17. public class Person  
  18. {  
  19.     public string name;  
  20.     public Person(string name)  
  21.     {  
  22.         this.name = name;  
  23.     }  
  24. }  

说说上面的程序产生的结果,以及产生这个结果的原因是什么?

帖子上面说面试了十个人,居然有十个人答错了,貌似非常不正常啊,但是说白了,这里面主要就是想了解一下面试者对引用传递和值传递的理解;

我在以前过过一篇关于引用传递和值传递的博客,地址如下:http://blog.csdn.net/luxin10/article/details/6195712

今天再就上面的面试题说一说,一方面巩固自己,另一方面方便心里没底的面试者彻底了解;

首先我们得清楚,在C#中,数据类型分为引用类型和值类型,值类型保存在堆栈中,引用类型稍微复杂点,引用类型分为两部分保存,引用类型的值保存在托管堆中,对该值的引用保存在堆栈中,值和值引用构成了一个完整的引用类型变量;我们经常使用下面的语法声明变量:

  1. int i=0;  
  2. string str = "new string";  

在i的声明过程中,系统做了两件事情,一件事情是在内存的堆栈中找到一个4字节的位置(int类型的长度为4字节),转换成代码应该这么表示:int i= new int();第二件事情是将0赋予i,转换成代码应该这么表示:i=0;综合下来实际代码应该如下所示:

  1. int i=new int();  
  2. i=0;  

在str的声明过程中,系统也是做了两件事情,只不过两件事情的手段不一致,第一件事情是在内存中找了两个地方,一个地方在托管堆中,用于存储str的值,另外一个地方在堆栈中,用于指向托管堆的存储位置;转换成代码也是一句话:string str=new string();第二件事也是将new sting这个字符串赋予变量值,但是new string是记录在托管堆中的,这是与值类型的区别,转换成代码应该这么表示:str=“new string”;综合下来代码是一样的,但是在分配内存时存在的差异就比较大了:

  1. string str=new striing();  
  2. str="new string";  

弄清楚了值类型和引用在内存中的保存方式,我们再来看看方法的参数传递,在C#中,所有的参数都是通过值来传递的,被调用的方法得到的都是该值的副本;这里一定要注意:被调用的方法得到的都是该值的副本,也就是说,我们看下面这个方法:

  1. public void MethDouble(int i)  
  2. {  
  3.     return i*2;  
  4. }  

下面我们在程序中调用该方法,代码如下:

int i=3;

MethDouble(i);

在以上的代码中,系统做了如下事情:首先将变量在内存堆栈中copy一个副本,这个副本的变量名我们假设称为copy_i,得到副本后,再将副本copy_i传递给方法MethDouble执行;所以在以上的过程中,MethDouble方法内部所做的任何事情都不会对变量i产生任何影响;我们再来看看下面的方法:

  1. <span style="white-space:pre">    </span>public void StrDouble(string str)  
  2.     {  
  3.         return str+str;  
  4.     }  

下面我们在程序中调用该方法,代码如下:

  1. string str = "myString";  
  2. StrDouble(str);  

同理,在以上的代码中,系统做了如下事情:首先将str在堆栈中的值引用变量copy一个副本,这个副本变量名我们假设称为copy_str,这个副本是一个引用,该引用指向的地址就是str引用指向的托管堆的地址,也就是托管堆中“myString”值,得到副本后,再将副本copy_str传递给方法执行,所以在操作过程中,方法对副本所做的修改都会直接修改托管堆中的值,从而影响方法外的str变量;

明白了以上的原理,我们再来看看开篇的面试题:

  1. int num = 0;  
  2. Person p = new Person("Li");  
声明了值类型变量num,引用类型变量p;

  1. A1(p, num);  
将num的副本和p的引用副本传递给方法;

这里我们可能有疑问了,p的引用传递进去了,那么对p所做的修改应该会影响托管堆中的值啊,我们这里看看方法A1方法的实现

  1. static void A1(Person p, int num)  
  2. {  
  3.     p = new Person("Wang");  
  4.     num = 1;  
  5. }  
注意了,上面的方法其实是用了一个障眼法,我把方法改改,大家再看看:

  1. static void A1(Person person, int num)  
  2. {  
  3.     person = new Person("Wang");  
  4.     num = 1;  
  5. }  
没改之前的方法里面一句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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值