.Net 值类型和引用类型的一些认识
参考:http://www.cppblog.com/luyulaile/archive/2011/04/08/143703.html
前言
C#的值类型包括:结构体(数值类型,bool型,用户定义的结构体),枚举,可空类型。
C#的引用类型包括:数组,用户定义的类、接口、委托,object,字符串。
数组的元素,不管是引用类型还是值类型,都存储在托管堆上。
值/引用类型的存储
栈(线程堆栈)是内存中完全用于存储局部变量或成员字段(值类型数据)的高效的区域,但其大小有限制。
托管堆所占内存比栈大得多,当访问速度较慢。托管堆只用于分配内存,一般由CLR(Common Language Runtime)来处理内存释放问题。
当创建值类型数据时,在栈上分配内存;
当创建引用型数据时,在托管堆上分配内存并返回对象的引用。注意这个对象的引用,像其他局部变量一样也是保存在栈中的。该引用指向的值则位于托管堆中。
如果创建了一个包含值类型的引用类型,比如数组,其元素的值也是存放在托管堆中而非栈中的。当从数组中检索数据时,获得本地使用的元素值的副本,而该副本这时候就是存放在栈中的了。所以,不能笼统的说“值类型保存在栈中,引用类型保存在托管堆中”。
值类型和引用类型的区别:引用类型存储在托管堆的唯一位置中,其存在于托管堆中某个地方,由使用该实体的变量引用;而值类型存储在使用它们的地方,有几处在使用,就有几个副本存在。
对于引用类型,如果在声明变量的时候没有使用new运算符,运行时就不会给它分配托管堆上的内存空间,而是在栈上给它分配一个包含null值的引用。对于值类型,运行时会给它分配栈上的空间,并调用默认的构造函数,来初始化对象的状态。
备注:1. 值类型数组虽然分配在堆上,但数组元素依然是值类型,并没有被装箱。
2, 引用对象的值类型成员也随对象一起分配在堆上,同样也还是值类型,没有被装箱
C# code
using System; namespace ConsoleApplication3 { //值类型 struct SomeVal { public int x; } //引用类型 class SomeRef { public int x; } class Program { static void Main(string[] args) { SomeRef r1 = new SomeRef();//初始化,在堆中分配 SomeVal v1 = new SomeVal();//初始化,在栈上分配 Console.WriteLine(r1.x);//r1字段初始化为0 Console.WriteLine(v1.x);//v1字段初始化为0 r1.x = 5;//提领(解析)指针 v1.x = 5;//在栈上直接修改 SomeRef r2 = r1;//只复制引用(指针) SomeRef r3 = new SomeRef();//在堆中分配 SomeVal v2 = v1;//现在栈上分配,然后拷贝成员 r1.x = 8;//修改r1.x和r2.x //r2.x = 100;//修改r1.x和r2.x v1.x = 9;//修改v1.x而不修改v2.x r3.x = 10; Console.WriteLine(r1.x);//8 Console.WriteLine(r2.x);//8 Console.WriteLine(v1.x);//9 Console.WriteLine(v2.x);//5 Console.WriteLine(r3.x);//10 Console.ReadKey(); } } }
预定义的引用类型
1、Object类
这是所有值类型和引用类型的最终基类,因为所有的类型派生自Object,所以可以把任何类型转换为Object类型,甚至值类型也可以转化(装箱)
所有的值类型都派生自引用类型
2、String类
注意,string类型是属于引用类型。我们来看下面一段代码,在修改一个字符串的时候,实际上是创 建了一个新的字符串,而并非修改了原来在字符串。我们来看一个示例:
using System; namespace Ref { class StringRef { static void Main() { string str1 = "www"; string str2 = str1; Console.WriteLine("str1=" + str1); Console.WriteLine("str2=" + str2); str1 = "www.baidu.com.cn"; Console.WriteLine("str1=" + str1); Console.WriteLine("str2=" + str2); Console.ReadKey(); } } }
输出显示
str1="www";
str2="www";
str1="www.baidu.com.cn";
str2="www";
这和我们所预期的引用类型正好相反,为什么呢?
因为当我们用“www”来初始化str1的时候,就在堆上分配了一个
string对象,当初始化str2的时候,也指向了这个对象。当str1改变的时候,并不是修改了原有的对 象,而是新创建了一个对象,但str2还是指向原来的对象,所以,str2的值并未改
值/引用类型的初始化问题
参考:http://blog.csdn.net/imfzp/article/details/4158826
C# code
类中定义公共字段
using System; namespace ConsoleApplication1 { class Program { static int a ; static object b; static void Main(string[] args) { Console.WriteLine(a); Console.WriteLine(b); Console.ReadKey(); } } }
可以编译通过
方法中定义私有字段
using System; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { int a; object b; Console.WriteLine(a); Console.WriteLine(b); Console.ReadKey(); } } }
编译无法通过
原因在于在class的构造器会自动对所有field自动初始化,所以代码1编译可以通过,代码2方法里的私有字段确无法通过编译。
值类型初始化全部为0.....引用类型初始化为null
默认列表:http://msdn.microsoft.com/zh-cn/library/83fhsxwc.aspx
数组的初始化
C#通过将初始值括在大括号 ({}) 内为在声明时初始化数组提供了简单而直接了当的方法。特别要注意的是,如果声明时未初始化数组,则数组成员自动初始化为该数组类型的默认初始值。
同理,double[](或int[]...等)初始化为0;
字符串的初始化
不存在无参数的构造函数
字符串的创建有8个方法的重载,7个需要输入char[],还有一个需要2个参数。。
常用方法是直接对其赋值初始化
string s = “initial”;
值/引用类型初始化的内存分配流程
比如创建一个对象:
Customer cus;
cus = new Customer();
申明一个Customer的引用cus,在堆栈上给这个引用分配存储空间。这仅仅只是一个引用,不是实际的Customer对象!
cus占4个字节的空间,包含了存储Customer的引用地址。
接着分配堆上的内存以存储Customer对象的实例,假定Customer对象的实例是32字节,为了在堆上找到一个存储Customer对象的存储位置。
.NET运行库在堆中搜索第一个从未使用的,32字节的连续块存储Customer对象的实例!
然后把分配给Customer对象实例的地址赋给cus变量!
参考:http://bbs.bccn.net/thread-239785-1-1.html
例如声明string
1、string s;
声明string s;只是在栈区存在引用,但是堆区没有分配内存,也就没有指向
没有指向的引用没有任何意义
2、string s = null;
在堆中开辟内存空间(但应该很小吧),没有值但是有内存地址
值类型的声明
例如声明int
int i;
告诉编译器需要给我分配4字节的内存空间来存储i的值,在栈区开辟内存,但是没有任何值