从一道面试题说说方法的引用传递和值传递

今天偶然在csdn论坛看到这么一篇帖子,帖子地址:http://topic.csdn.net/u/20090402/14/1127EB2A-3693-4D2C-8705-18C08E216177.html

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

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://blog.csdn.net/luxin10/article/details/6195712

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

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

	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;

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

	string str=new striing();
	str="new string";

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

	public void MethDouble(int i)
	{
		return i*2;
	}

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

int i=3;

MethDouble(i);

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

	public void StrDouble(string str)
	{
		return str+str;
	}

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

	string str = "myString";
	StrDouble(str);

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

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

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

        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 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");
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值