Breathe李

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

值类型和引用类型、装箱与拆箱

       本想把这篇博文题目的拆开来说,但是想一想,值类型和引用类型、装箱与拆箱又是密不可分的,于是决定还是放在一起来说。

一、  值类型和引用类型:

在我们刚开始学习写程序的时候,面向对象的三大概念等都是我们所能熟悉,并且比较好理解的概念,但是,到值类型和引用类型的时候,我相信有大部分的同仁都曾经迷茫过(包括我^_^)

在我们之前说的基元类型中,无非分为两大类型,一个就是值类型,另一个则是引用类型。我们先说一下引用类型,首先,我们需要非常明确的一点就是引用类型是从托管堆上面分配空间的而值类型是在一个线程堆栈上分配空间的(值类型变量做为局部变量时,该实例将被创建在堆栈上;而如果值类型变量作为类型的成员变量时,它将作为类型实例数据的一部分,同该类型的其他字段都保存在托管堆上)。当我们用new关键字去构建一个引用类型的时候,new操作符返回给我们的是一个对象数据的内存地址,我们也可以理解为一个对象指针。CLR会对我们构建的引用类型对象的成员进行初始化的操作并将引用类型对象中的字段进行初始化操作为0或者null,还有一点我们必须注意的是,在我们构建完一个引用类型的时候,很可能会增加一次GC(垃圾回收)的操作,就是说,当我们这个引用类型的对象被认为不会再使用的时候,将被垃圾回收,以释放占用的内存空间(关于垃圾回收,将在以后的博文中进行讨论)然而值类型是在现成堆栈上进行分配的,所以它没有一个对象指针,我们操作值类型就是操作值类型中的数据。并且,值类型不受GC垃圾回收的制约。我们来举个例子,并将一步一步的进行分析。代码如下:

static void Main(string[] args)
{
String str_a = "abc";//在托管堆上分配内存
Int32 int_a = 123;//在线程堆栈上分配内存
String str_b = str_a;//复制str_a的对象指针
Int32 int_b = int_a;//在线程堆栈上分配内存并复制成员
Console.WriteLine(str_a);//打印str_a的值为abc
Console.WriteLine(str_b);//打印str_b的值为abc
//这里不再打印int_a和int_b的值
str_b = "uio";
int_b = 456;
Console.WriteLine(str_a);//打印str_a的值为abc
Console.WriteLine(str_b);//打印str_b的值为uio
Console.WriteLine(int_a);//打印int_a的值为123
Console.WriteLine(int_b);//打印int_b的值为456
Console.ReadLine();
}

通过以上代码我们可以看到,当我们分别从托管堆上分配内存给str_a的时候str_a储存的并不是“abc”而是”abc”的内存地址,同样将str_a的值赋给str_b的时候,str_b储存的并不是”abc”而是从str_a那里复制到”abc”的内存地址,所以,我们第一次打印str_a和str_b的时候,打印出来的结果是一样的,都是”abc”。然而,当我们把str_b的值更改为”uio”的时候,str_b储存的内存地址就为”uio”的内存地址,而str_a没有做任何改变,所以,第二次打印str_a和str_b的时候,结果为”abc”和“uio”。

       那么,值类型是怎么回事呢?值类型存储中,并不包含一个对象的指针,而是它本身的一个字段,也就是说当int_a赋值给int_b的时候,int_b把int_a的所有成员全部进行复制在一个新的内存空间,而int_b的修改操作,并不影响int_a。

       以上为笔者对值类型和引用类型的理解,有什么错误的地方也请大家指出。

一、  装箱和拆箱

下面我们来讨论下装箱和拆箱,装箱和拆箱是指:从值类型转换成引用类型的操作,我们称之为装箱,反之,从引用类型转换成值类型,我们称之为拆箱操作。

       理论上,这两句话应该很好理解,实际上其实也不难。我们来看一个例子                 

如下:

static void Main(string[] args)
{
Int32 int_a = 5;
object o = int_a;
int_a = 123;
o = 456;
Console.WriteLine(int_a+","+(Int32)o);
Console.ReadLine();
}

我们来分析下这段代码执行了几次装箱和几次拆箱。首先,我们给int_a赋值为5,并且把int_a赋值给object类型,这个时候就发生了一次装箱操作,就是说把int_a的值5,分配相应的内存复制到托管堆上,并把值为5的内存地址指针返回给object类型,这是一次装箱操作,在装箱操作的时候,int_a还在线程堆栈上并且值为5。Int_a=123只是单纯的更改了原来值。o=456这里面又进行了一次装箱操作,就是说int类型456装箱成引用类型(操作与o=int_a一样),并把o的指针引用更改为456的指针引用。之前123将等待垃圾回收。接下来,看打印输出的方法。在这里面,我们其实传入的参数都为String类型,所以在这里int_a也执行了装箱操作,因为与String类型的“,”进行连接操作。还有一个装箱操作就是在(Int32)o这里面,我们将o强制类型转换是一个拆箱操作(引用类型转换成值类型为拆箱操作),这也是整个Main方法中的唯一一次拆箱操作,那么在转换成值类型之后,又与String类型的”,”进行连接操作,在这里就又执行了一次装箱操作。最后返回给WriteLine方法的是一个字符串。打印出来为“123,456”所以上述代码进行了4次装箱操作和1次拆箱操作。为了更清晰的表现给大家,直接上IL代码的图:

 

装箱操作我已经使用方框标识出来,拆箱操作我已经使用横线标识出来。

       对于装箱与拆箱操作,上述代码可以看出,为了提高上段代码的效率,我们没有必要在执行WriteLine方法的时候对o进行一次拆箱操作。至于为什么这么写只是为了更清晰的演示装箱与拆箱操作在编译为IL代码的时候是如何表示的。

       我想这篇博文也到此结束了,可能没有很深入的讨论到值类型与引用类型、装箱和拆箱操作,只是点到为止吧,更深层次的东西,也希望大家能够自己进行一个研究。必要的时候看一下IL代码,会了解到很多我们之前不知道的东西。如有错误、问题也请各位看官留言告诉我。谢谢!

      

 

       在这里再次为论坛做个宣传 www.175m.com,希望大家可以在论坛里面一起建设、讨论该论坛(希望博客园的大哥们,不要以为我在挖墙角,我只是在为我们程序员的大家庭做贡献)



posted on 2011-12-28 10:43  LouisLee  阅读(1438)  评论(7编辑  收藏  举报