* 如何决定放哪儿?
这里有一条黄金规则:
1. 引用类型总是放在堆中。(够简单的吧?)
2. 值类型和指针总是放在它们被声明的地方。(这条稍微复杂点,需要知道栈是如何工作的,然后才能断定是在哪儿被声明的。)
就像我们先前提到的,栈是负责保存我们的代码执行(或调用)时的路径。当我们的代码开始调用一个方法时,将放置一段编码指令(在方法中)到栈上,紧接着放置方法的参数,然后代码执行到方法中的被“压栈”至栈顶的变量位置。通过以下例子很容易理解...
下面是一个方法(Method):
public int AddFive(int pValue) { int result; result = pValue + 5; return result; } |
现在就来看看在栈顶发生了些什么,记住我们所观察的栈顶下实际已经压入了许多别的内容。
首先方法(只包含需要执行的逻辑字节,即执行该方法的指令,而非方法体内的数据)入栈,紧接着是方法的参数入栈。(我们将在后面讨论更多的参数传递)
接着,控制(即执行方法的线程)被传递到堆栈中AddFive()的指令上,
当方法执行时,我们需要在栈上为“result”变量分配一些内存,
Themethod finishes execution and our result is returned.
方法执行完成,然后方法的结果被返回。
通过将栈指针指向AddFive()方法曾使用的可用的内存地址,所有在栈上的该方法所使用内存都被清空,且程序将自动回到栈上最初的方法调用的位置(在本例中不会看到)。
在这个例子中,我们的"result"变量是被放置在栈上的,事实上,当值类型数据在方法体中被声明时,它们都是被放置在栈上的。
值类型数据有时也被放置在堆上。记住这条规则--值类型总是放在它们被声明的地方。好的,如果一个值类型数据在方法体外被声明,且存在于一个引用类型中,那么它将被堆中的引用类型所取代。
来看另一个例子:
假如我们有这样一个MyInt类(它是引用类型因为它是一个类类型):
public class MyInt
{
publicint MyValue;
}
然后执行下面的方法: public MyInt AddFive(int pValue) { MyInt result = new MyInt(); result.MyValue = pValue + 5; return result; } |
就像前面提到的,方法及方法的参数被放置到栈上,接下来,控制被传递到堆栈中AddFive()的指令上。
接着会出现一些有趣的现象...
因为"MyInt"是一个引用类型,它将被放置在堆上,同时在栈上生成一个指向这个堆的指针引用。
在AddFive()方法被执行之后,我们将清空...
我们将剩下孤独的MyInt对象在堆中(栈中将不会存在任何指向MyInt对象的指针!)
这就是垃圾回收器(后简称GC)起作用的地方。当我们的程序达到了一个特定的内存阀值,我们需要更多的堆空间的时候,GC开始起作用。GC将停止所有正在运行的线程,找出在堆中存在的所有不再被主程序访问的对象,并删除它们。然后GC会重新组织堆中所有剩下的对象来节省空间,并调整栈和堆中所有与这些对象相关的指针。你肯定会想到这个过程非常耗费性能,所以这时你就会知道为什么我们需要如此重视栈和堆里有些什么,特别是在需要编写高性能的代码时。
Ok...这太棒了, 当它是如何影响我的?
Goodquestion.
当我们使用引用类型时,我们实际是在处理该类型的指针,而非该类型本身。当我们使用值类型时,我们是在使用值类型本身。听起来很迷糊吧?
同样,例子是最好的描述。
假如我们执行以下的方法:
public int ReturnValue() { int x = new int(); x = 3; int y = new int(); y = x; y = 4; return x; } |
我们将得到值3,很简单,对吧?
假如我们首先使用MyInt类
public class MyInt { public int MyValue; } |
接着执行以下的方法:
public int ReturnValue2() { MyInt x = new MyInt(); x.MyValue = 3; MyInt y = new MyInt(); y =x; y.MyValue =4; return x.MyValue; } |
我们将得到什么?... 4!
为什么?... x.MyValue怎么会变成4了呢?... 看看我们所做的然后就知道是怎么回事了:
在第一例子中,一切都像计划的那样进行着:
public int ReturnValue() { int x = 3; int y = x; y = 4; return x; } |
在第二个例子中,我们没有得到"3"是因为变量"x"和"y"都同时指向了堆中相同的对象。
public intReturnValue2() { MyInt x; x.MyValue = 3; MyInt y; y =x; y.MyValue = 4; return x.MyValue; } |
希望以上内容能够使你对C#中的值类型和引用类型的基本区别有一个更好的认识,并且对指针及指针是何时被使用的有一定的基本了解。在系列的下一个部分,我们将深入内存管理并专门讨论方法参数。