值类型
且看下面的类型定义代码:
public struct MyStruct
{
/* 注意:作为结构,内部字段是不能象下面所写那样,在声明时直接初始化的。
* 但这里为了节省篇幅,从表达语义的角度,直接在声明时初始化了
* 此结构的代码无法通过编译的 */
public int i = 5; //值类型
public System.Exception ex = new Exception(); //引用类型
}
在 MyStruct结构中,有2个字段,一个是值类型的i变量,一个是引用类型的ex变量。这种情况下,内存中应该是一个什么模样呢?
首先,变量 i和ex作为MyStruct的成员,必然存放在MyStruct实例的内部,而变量i作为值类型,其值就存放在自身;ex作为引用类型,变量内只存放实例(对象)的引用,而实例(对象)则在堆上创建,因此就有如图-5所示:
引用类型
且看下面的类型定义代码:
public class MyClass
{
MyStruct ms = new MyStruct(); //上面所述的MyStruct结构
System.Random r = new Random(); //引用类型
}
在 MyClass中,有2个字段成员,一个是我们上面的所定义的MyStruct结构值类型ms,另外一个是Random类类型r。
这里我们把情况再变得复杂一些了,因为 MyStruct内部还有值类型和引用类型的字段,这时候内存中是一幅什么景象呢?我们要记住,不管情况多么复杂,把握住值类型和引用类型的特点,慢慢分析,总会得到正确的结果,正如图-6所示:
作为引用类型的实例(对象),无论什么情况,都是在堆中的。而 MyStruct结构作为MyClass的成员,它也在MyClass实例所占的堆内存中,而且因为值类型的值是在自身存放的,所以就是图-6中看到的结果。整个图-6,所有的值类型和引用类型的布局,都完全负责值类型和引用类型的特点,没有例外。
总结
以前在问起值类型和引用类型有什么区别的时候,经常听到同学说“值类型存放在栈上,引用类型存放在堆上”。其实这么说并不严谨,因为当值类型作为引用类型的一个成员的时候,它的值是内嵌在引用类型实例内部在堆上存放的。我认为,正确的说法应该是:值类型变量的值存放在变量内部,而引用类型变量的值存放在堆上,变量本身存放一个指向堆中的值的引用。同时我们也可以看到在 2个变量赋值的时候,值类型和引用类型的差别,值类型将自身的值复制给对方,之后,2方互不相干;引用类型把引用复制给对方,从而双方都指向同一个堆中的实例,其中任何一方对实例做出修改,都会在另一方的操作中得到反映。最后我们通过复杂类型的内部成员的内存布局情况,进一步了解了值类型和引用类型的内存布局情况。