值类型与引用类型及在内存中的存储

来源:互联网 发布:怎样成为淘宝会员 编辑:程序博客网 时间:2024/05/22 15:27
FROM MSDN:

如果数据类型在它自己分配的内存中存的是具体数据,则该数据类型就是“值类型”;如果存储的是指向一个地址的指针,那么该数据类型是“引用类型”。

值类型

值类型包括:

  • 所有数字数据类型

  • BooleanChar 和 Date

  • 所有结构,即使其成员是引用类型

  • 枚举,因为其基础类型总是 SByteShortIntegerLongByteUShortUInteger 或 ULong

引用类型

引用类型包括:

  • String

  • 所有数组,即使其元素是值类型

  • 类类型,如 Form

  • 委托

 public string get()        {            mypost op = new mypost();            mypost opp = op;            op.subject = "I am right!";                       string s = "Iamright_str";            set(op,ref s);            return op.subject + "" + s + "" + opp.subject;        }        public void set(mypost op ,ref string str)        {            mypost o = op;            op.subject = "I am wrong!";            str = "Iamwrong_str!";        }

调用GET() 输出:I am wrong!Iamwrong_str!I am wrong!

因为OP,OPP都是引用类型,所以当类的实例OP作为参数传递给SET时,传递的是一个地址,如果实际储存的值(实例)改变,他会跟着改变。OPP存储的地址与OP一致。如图:


但是注意这里,string参数需要加上ref才是传递地址,否则会像值类型一样不改变内容。

这是因为string是特殊的引用类型(编译器对其做了特殊处理),不然MSDN也不会把它单独作为一类。

string最为显著的一个特点就是它具有恒定不变性:我们一旦创建了一个string,在managed heap 上为他分配了一块连续的内存空间,我们将不能以任何方式对这个string进行修改使之变长、变短、改变格式。所有对这个string进行各项操作(比如调用ToUpper获得大写格式的string)而返回的string,实际上另一个重新创建的string,其本身并不会产生任何变化。 
string   对象称为不可变的(只读),因为一旦创建了该对象,就不能修改该对象的值。有的时候看来似乎修改了,实际是string经过了特殊处理,每次改变值时都会建立一个新的string对象,变量会指向这个新的对象,而原来的还是指向原来的对象,所以不会改变。这也是string效率低下的原因。

值类型和引用类型在内存中的存储

MSDN值类型的内容存储在栈(stack)上分配的内存中...引用类型(例如,类或数组的实例)在另一个称为堆(heap)的内存区域中分配

在我们使用类型时,代码里面必然少不了变量的声明,我们先看一下方法内的局部变量的声明,请看如下代码:

private static void Main(){         int i;         MyClass mc;         i = 5;         mc = new MyClass();}

当一个局部变量声明之后,就会在栈的内存中分配一块内存给这个变量,至于这块内存多大,里面存放什么东西,就要看这个变量是值类型还是引用类型了。

值类型

如果是值类型,为变量分配这块内存的大小就是值类型定义的大小,存放值类型自身的值(内容)。比如,对于上面的整型变量i,这块内存的大小就是4个字节(一个int型定义的大小),如果执行i = 5;这行代码,则这块内存的内容就是5(如图-1)。

对于任何值类型,无论是读取还是写入操作,可以一步到位,因为值类型变量本身所占的内存就存放着值。

引用类型

如果是引用类型,为变量分配的这块内存的大小,就是一个内存指针(实例引用、对象引用)的大小(在32位系统上为4字节,在64位系统上为8字节)。因为所有引用类型的实例(对象、值)都是创建在堆上的,而这个为变量分配的内存就存放变量对应在堆上的实例(对象、值)的内存首地址(内存指针),也叫实例(对象)的引用。以图形化的方式展现仿佛是变量有一条线指向着它在堆中的实例(有如图-2),而如果变量的类型还没有被实例化,则为零地址(null、空引用)。

以下为执行mc = new MyClass();代码后,内存中的示例:

 

由图-2可知,变量mc中存放的是MyClass实例(对象)的对象引用,如果需要访问mc实例,系统需要首先从mc变量中得到实例的引用(在堆中的地址),然后用这个引用(地址)找到堆中的实例,再进行访问。需要至少2步操作才可以完成实例访问。

嵌套类型

“值类型存放在栈上,引用类型存放在堆上” 这个说法并不严谨,因为当类型嵌套时...且看下面的类型定义代码:

public struct MyStruct{          /* 注意:作为结构,内部字段是不能象下面所写那样,在声明时直接初始化的。        * 但这里为了节省篇幅,从表达语义的角度,直接在声明时初始化了        * 此结构的代码无法通过编译的 */         public int i = 5; //值类型         public System.Exception ex = new Exception(); //引用类型}

在MyStruct结构中,有2个字段,一个是值类型的i变量,一个是引用类型的ex变量。这种情况下,内存中应该是一个什么模样呢?

首先,变量i和ex作为MyStruct的成员,必然存放在MyStruct实例的内部,而变量i作为值类型,其值就存放在自身;ex作为引用类型,变量内只存放实例(对象)的引用,而实例(对象)则在堆上创建,因此就有如图-5所示:

这样,值类型MyStruct的引用类型成员在内存上的地址是在Heap上的。同理,引用类型中的值类型其存储地址仍然是Heap而不是Stack。因此更严谨的说法应该是:

值类型变量的值存放在变量内部:若是局部变量,在Stack上;若是引用类型的成员,则在Heap上

引用类型变量的内容存放在堆上(always),变量本身在栈上存放一个指向堆中的引用



原创粉丝点击