81

【基础中的基础】引用类型和值类型,以及引用传递和值传递 - 自由飞

 6 years ago
source link: http://www.cnblogs.com/freeflying/p/8005959.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

【基础中的基础】引用类型和值类型,以及引用传递和值传递

一直在博客园怼人,非常惭愧。所以郑重决定:

好好写一篇干货,然后再接着怼人。

这是一起帮陈百万同学的求助,讲了一会之后,我觉得很有些普世价值,干脆就发到园子来。面向小白,高手轻拍。

我们从最简单的说起(基础知识,懂的同学直接往下拉),直接上代码:

static int amount;
static void AddTV(int amount)
{
amount++;
Console.WriteLine("方法中,amount="+amount);
}

然后,我们将参数amout传入AddTV()方法,希望能让其+1

amount = 10;
AddTV(amount);
Console.WriteLine("AddTV(amount)执行之后,amount=" + amount);

 那么执行的结果呢?

49387-20171208152736452-631623057.png

amount的数量并没有发生变化。

为什么没有变呢?

这是最入门的知识,通常的解释是:

amount是int类型,int是值类型,所以当它作为参数时,传递给方法的是它的一个副本(复制品),因此方法中改变的是它的副本的值,amount本身并没有改变。

OK,这完全没有问题。而且,如果想改变的值,就需要加 ref 关键字,如下所示:

static void AddTV(ref int amount)
{
amount++;
Console.WriteLine("方法中,amount="+amount);
}

大家自己跑一下,看看结果有什么不一样。

这叫做参数的引用传递。

这是最基础的知识,非常清晰。好的,接着,C#是面向对象的语言嘛,我们要引入一个对象。

public class House
{
public int TVAmount { get; set; }
}

然后,我们把House对象作为参数传递,值传递,不带ref的。如下所示:

static void AddTV(House house)
{
house.TVAmount++;
Console.WriteLine("方法中,house.TVAmount=" + house.TVAmount);
}
House house = new House();
AddTV(house);
Console.WriteLine("AddTV(house)执行之后,house.TVAmount=" + house.TVAmount);

执行之后你会发现:

49387-20171208153848015-198892622.png

咦?house.TVAmount的值变了耶!

为什么呢?

有的同学听到的解释是这样的:

House是对象,是引用类型,引用类型作为参数传递到方法中,它的值会被方法改变。

有些同学,哦!记住了:值类型传进去不变,引用类型传进去要变,但值类型引用传递又要变……虽然有点绕,但死背下来也行。

但有的同学就开始开始抛问题了:(这种同学特别值得表扬!安利一篇我的文章:讲课这些天(五)怎么才能把代码写好?

值类型的引用传递,和引用类型的值传递,效果都一样,那他们有什么区别呢?

Good question!

实际上,死背上面的,是会出问题的,我还是用代码展示一下:

static void AddTV(House house)
{
house = new House();
house.TVAmount++;
Console.WriteLine("方法中,house.TVAmount=" + house.TVAmount);
}

这样写,眼尖的同学一眼就能看出差别:这一次方法体内多了一个:house = new House();

不要以为这是抽风啊,实际的开发代码中,各种各样的原因,很多时候都确实会在方法体内重新new一个参数实例的。

那运行结果怎么样的呢?

49387-20171208161623843-995563755.png

怎么样?!引用类型也不好使了?现在,是不是

49387-20171208161946234-2093133520.png

不像JavaScript到处都是bug和设计缺陷(是的,日常黑js一百年,),C#是一门严谨清晰的语言,不会有什么“灵异”事件。现象和你的想法不一致,一定是你的想法出了问题。

所以,要真正地弄明白这里面的道道,我们还是要回到原点:

首先的首先,看看这代码,你真的明白是什么意思么:

House house
=
new House();

我为什么要写成三行?

因为这其实是三个过程:

  • House house 这是声明了一个变量
  • new House() 这是生成了一个对象
  • = 把 house 和 new House() 关联起来

注意,注意我用的是“关联”,很多人喜欢说“赋值”,甚至“等于”,这就容易造成我们理解上的误区。

为了理解这种关联,我画了一幅图:

49387-20171208164010671-1730182482.png

观察这幅图,house和New House,是不同的数据储存。事实上,在house里面,有一个记录了new Houuse()存储位置的“引用”(reference,这个英文单词有助于我们理解)。所以,当我们house.TVAmount的时候,是通过house找到new House(),然后得到new House()的数据进行操作。

不知道大家能不能明白这一点?

作为对比,我们来看看值类型是怎么样子的。

49387-20171208164443718-1157787773.png

整个这一块都是int i,int i 里面就直接的存储了10这个数据,没有引用,int i里直接存放数值10,所以叫做“值类型”。

好了,理解了上面的概念之后,我们回头来看方法参数。

C#的说法非常的清晰,只看有没有 ref 关键字:

  1. 不带ref的,一定是“值传递”
  2. 带ref的,一定是“引用传递”

和传递的是什么类型的参数,半毛钱关系没有。

关键是,你要知道:当参数为引用类型时,传递的不是对象(new House()),而是对象的引用(house)

  • 如果是值传递,传递的是 对象引用的 副本
  • 如果是引用传递,传递的是 对象引用 本身

什么叫做对象引用的副本呢?还是给一幅图:

49387-20171208171516765-2048117433.png

 明白了吧?作为参数的house的副本,还是指向的New House对象,所以,在方法体中使用:house.TVAmount++,最终修改的还是原来的new House()里面的值。

但是,当你在方法体中:house = new House(); 你实际上就干了件啥事呢?

49387-20171208172122546-731412336.png

然后,你再:house.TVAmount++,改变的是新的House对象的值啊!(请结合英文单词 new 来理解这一点)

所以,原来的 house 引用指向的对象,就根本没有发生改变。

希望你仍然还保持着清醒的头脑,没有被我弄晕,O(∩_∩)O~

这样我们就可以接着往下走。那假如我们既要保留方法体内的:house = new House(); 又要通过方法,改变传入对象的值,我应该怎么办呢?

干脆留作思考题吧? o( ̄┰ ̄*)ゞ 

请同学们在理解原理的基础上自己去写一写,跑一跑,仔细的体会体会。

最后,为了更清晰直观的看到所谓“对象的引用”的变化,我给大家一个神器:在调试时使用一元运算符 & 来查看变量的内存地址:

49387-20171208173151952-1873322523.png
49387-20171208173316781-1499505406.png

好了,自己折腾去吧!

enjoy it。

周末发帖,送给爱学习的同学们!

+++++++++++++++++++++++++++++++

最后,悄悄的说一句:我们的 一起帮 有了好多新功能,不想去看一看?


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK