前言
值类型和引用类型都是相对于变量来说的,是变量存储数据的一种形式。
值类型变量直接存储数据将数据存储在栈中,而引用类型的变量存储的是数据的引用,其真正的数据存储在数据堆中。
栈与堆:
栈是在编译期间就分配好的内存空间,因此你的代码中必须就栈的大小有明确的定义。
堆是程序运行期间动态分配的内存空间,可以根据程序的运行情况确定要分配的堆内存的大小。
一、值类型(ValueType)
1、范围
数值类型:整型、int、long、byte、char、double
结构体:struct、用户定义的结构体
bool型:
枚举:enum
2、说明
值类型的变量声明后,无论是否已经赋值,编译器都会为其分配内存空间。
每个变量的内存空间大小可以不同,用来直接存储数据本身(存放到栈上)。
当数据被释放时,期内存空间将会被回收。
每进行一次赋值操作,都会创建一个新对象,为其分配内存存储数据。
例如
声明一个变量a,为其赋值为20,这时在栈上就会分配到一个内存空间,来存储a的值20 。再声明一个变量b,让b=a,这时栈上会在分配出一个内存空间,用来存储b的值。当a的值改变是,不会影响b的值改变。当b的值改变时也不会影响到a的值改变。
二、引用类型(ReferenceType)
1、范围
包括数组、用户定义的类、接口interface、委托delegate、object、字符串string、null类型、类class
2、说明
引用类型的变量持有的是对数据的引用,即申请引用型变量后,内存会为其分配一个很小的空间(空间大小一般相等)用来存储地址(在栈中),而这个地址对应的就是数据在堆中的位置。引用型变量持有的就是这个数据的地址。当新的变量的值在堆中存在时,堆中并不会重新增加一个相同的值,而是在栈中会有一个新的内存来存放这个新变量所持有的引用(即原来数据在堆中的地址)。
变量并不会在创建他们的方法结束时释放内存空间,他们所占用的内存会被CLR中的垃圾回收机制释放。
每进行一个赋值操作都会创建一个新的引用。
例:
申请一个变量a,为其赋值为20 ,在堆中会分配出内存来存储20这个数据,20在堆中的地址会存储在栈中,a持有这个地址。再申请一个变量b,使b=a,栈中会将a持有的这个地址同时赋值给b,a和b同时拥有堆中20这个数据的地址。当改变b=10,堆中的这个20的数据会变为10,而此时a与b用的是同一个地址,所以此时a的值也会变为10.
三、值类型与引用类型的区别
角度 | 值类型 | 引用类型 |
---|---|---|
存储方式 | 直接存储数据本身 | 存储的是数据的引用,数据存储在堆中 |
内存分配 | 分配在栈中 | 分配在堆中 |
效率 | 高,不需要地址转换 | 低,需要地址转换 |
内存回收 | 使用完后立即回收 | 使用完后不立即回收,而是给GC处理 |
赋值操作 | 创建一个新对象 | 创建一个引用 |
类型扩展 | 不易扩展,所有类型都是封闭的,无法派生出新的类型 | 具有多态性,方便扩展 |
实例分配 | 在线程栈上分配,静态分配(有时可以动态) | 在进程堆中分配,动态分配 |
总结:
值传递仅仅传递的是值,不影响原始值。
引用传递,传递的是内存地址,修改后会改变内存地址对应储存的值。
四、装箱和拆箱
1、装箱
将值类型转变成引用类型
在堆上为新生成的对象(该对象包含数据,对象本身没有名称)分配内存。
将堆栈上值类型变量的值拷贝到堆上的对象中。
将堆上创建的对象的地址返回给引用类型变量(从程序员角度看,这个变量的名称就好像堆上对象的名称一样)。
2、拆箱
将引用型变量变成值类型
将引用类型变量堆上的值拷贝到栈上面。
3、比较
名称 | 值类型 | 引用类型
表示类型 | 基本类型 | 类,数组,接口 ,C#特有的委托.
存储内容 | 值 | 值的引用
存储位置 | 堆栈 | 托管堆
五、传参
在C#中很多变量都是按值传递的,但是也有个别的变量是引用类型的,方法传递参数时加上ref(out),为引用传递参数,使用时都会改变原来参数的数值。
ref可以把参数的数值传递进函数,需要在外面进行初始化,但是out是要把参数清空,就是说无法把一个数值从out传递进去的,out进去后,参数的数值为空,所以必须在函数内部初始化一次。
说明:
1、使用ref型参数时,传入的参数必须先被初始化。对out而言,必须在方法中对其完成初始化。
2、使用ref和out时,在方法的参数和执行方法时,都要加Ref或Out关键字。以满足匹配。
3、out适合用在需要retrun多个返回值的地方,而ref则用在需要被调用的方法修改调用者的引用的时候。
好多博客上说ref是有进有出,out是只进不出,想一想好像真的是这样。