最近在读一本叫做 Professional c# edition2 的古董书,是2002年对于c#1.0做系统性描述的一本书。
里面有许多内容很有趣。不过很遗憾的是里面对于一些内容竟然很明确的注明:这些东西是作者实验得出的结论。
很显然作者并不是一个c#委员会成员or complier的编译者。
本文的所有内容均取自此书[1]Simon Robinson, K. Scott Allen etc.., Karli Watson: Professional C# 2nd Edition, March 2002
如有错误,敬请更正。

0. 本篇内容

这里介绍的一个内容是,在c#和java中,为什么int就是值传递(by value),而StringBuilder是参照传递(by reference)。
通过简单介绍stack和heap的存储概念,可以让你清楚的理解上面一行的问题的答案。
(ps:这个在笔者初学java的时候困扰了笔者很久)
(ps2:如果笔者有错误,请留言指出)
(ps3:留言需要翻墙,谢谢)

int i = 3;
StringBuilder s = new StringBuilder("str");

int j = i;
StringBuilder cp = s; // cp = "str"

i = 2; // j is 3
s.reverse();// cp = "rts"

1.heap和stack的再温习

对于int而言,int大致是这样在内存中存在的。
在程序中,int在内部被储存为一个pointer,指向内存的stack中的int。
所以是
int pointer -> int in stack

stack中有一个特点,就是在声明的时候,要明确的声明他的大小。
这也是int,long等等会有明确的大小的原因。

stack还有一个特点,就是在超出他的生命周期范围的时候,他会自动被删除。
所以这个也用stack的数据结构来实现,这个下面会讲。

对于StringBuilder而言,它大致是这样在内存中存在的。
在程序中,StringBuilder也是一个pointer,指向内存的stack中的Stringbuilder。
不过在stack中的Stringbuilder是另一个pointer,它指向heap中的真实的Stringbuilder的位置。
在stack中,不仅仅只有指向heap的pointer,同时也储存了一些额外的信息,比如Stringbuilder的属性啊等等。
这些信息是可以有固定的大小的,所以可以在stack中被方便的声明。而在heap中存储的是Stringbuilder的内部的数据,“rts”等等。
所以对于Stringbuilder而言,是这样的
Stringbuilder pointer -> Stringbuilder pointer in stack -> Stringbuilder in heap

2.stack的生命周期

思考这样的一段代码。

void calc () {
  int j = 0;
  for(int i = 0; i < 3; i++){
    //do something
  }
}

我们知道,在for文结束之后,i就消失了。
我们再次使用i的时候,i就会处于未声明的状态。
没错,这就是stack的先进先出的数据结构的巧妙应用。

我们进入stack的顺序是j, i
不过我们把数据取出stack的顺序是i, j
这也造成了我们会在for循环结束之后就无法使用i。
因为i已经被从stack中取出来了。

i被取出的时点是在{}这个括号结束的时候。

3.heap的生命周期

我们知道,heap中的数据,它的大小是可变的。
在heap中宣言的空间不够用的时候,比如像String,它是再取一个双倍大小空间的继续使用。

我们还知道,在拥有gc的语言之中,heap是不需要手动管理的。
它的原理就是 在没有pointer指向heap中的空间的时候,gc会自动把这个空间标识为不使用空间,然后进行回收。
这也是在拥有gc的语言里面,想要手动管理内存,基本上只能用null赋值。

再详细讲一下它的原理。
假设heap中使用的空间是A。那么所有在stack中的指向heap中A的pointer,全部被删除的时候。空间A就会被标记为非使用空间。gc会找时间把它删除掉。
stack中的pointer的删除顺序遵循上面2.中的生命周期。
所以一个值一直被传送的话,它就不会被删除。

4.为什么是值传递?为什么是参照传递?

想必读到这里,已经对stack和heap有了一个大致的了解。(如果有图会更好,不过图片太占用空间了….)

那么为什么int是值传递,为什么StringBuilder是参照传递呢?
因为,在程序里面复制的时候,只是复制了指向stack的pointer而已。
因为int的值就在stack中,所以int的复制就是值复制。
因为StringBuilder在stack中是指向heap的pointer,所以StringBuilder的就是参照复制呀!

5.在网络上学习的话…

比如这里[2]もう参照渡しとは言わせない,有太多的不知所云的人在写一些东西。。。
说实话这家伙写的东西倒是正确的,但是不正确的介绍知识,而只是趾高气昂的表示自己的厉害和清高。。。
我第一次读这个文,真的觉得这个文的作者是个智障。
现在不觉得是个智障,不过我觉得应该是EQ低下。
(PS:按照[2]里面的这家伙的说法,那就不存在参照传递了)

[1]Simon Robinson, K. Scott Allen, Ollie Cornes, Jay Glynn, Zach Greenvoss, Burton Harvey, Christian Nagel, Morgan Skinner, Karli Watson: Professional C# 2nd Edition, March 2002
[2]もう参照渡しとは言わせない


Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)