什么是值类型和引用类型
- 值类型在线程栈分配空间,引用类型在托管堆分配空间
值类型与引用类型的区别
- 两类型的数据存储位置不同
- 在引用类型中嵌套值类型时,或者在值类型装箱的情况下,值类型的实例就会被分配到托管堆上
- 值类型继承自
ValueType
,ValueType
又继承自System.Object
;而引用类型则直接继承于System.Object
; - 值类型的内存不受
GC
控制,作用域结束时,值类型会被操作系统自行释放 - 若值类型为密封的(sealed),你将不能把值类型作为其他任何类型的基类;而引用类型则一般具有继承性,这里是指接口和类;
- 值类型不能为
null
值,它会被默认初始化为该值类型的默认值;而引用类型默认会初始化为null
值,表示不指向托管堆中的任何地址;对值为null
的引用类型的任何操作都会引发NullRefernceException
异常 - 由于值类型变量包含其实际数据,因此默认情况下,值类型之间的参数传递不会影响变量本身;而引用类型变量保存的是数据的引用地址,它们作为参数传递时,参数会发生改变,从而影响引用类型变量的值
几种类型嵌套情况
- 引用类型中嵌套定义值类型
- 如果类的字段类型是值类型,它将作为引用类型实例的一部分,被分配到托管堆中,但那些作为局部变量(如下代码中的
c
变量)的值类型,则仍然会被分配到线程堆栈中. - 代码示例
- 如果类的字段类型是值类型,它将作为引用类型实例的一部分,被分配到托管堆中,但那些作为局部变量(如下代码中的
//引用类型嵌套定义值类型的情况
public class NestedValueTypeInRef
{
//valuetype作为引用类型的一部分被分配到托管堆上
private int valuetype = 3;
public void method()
{
//C被分配到线程堆栈上
char c = 'c';
}
}
class Program
{
static void Main(string[] args)
{
NestedValueTypeInRef typeInRef = new NestedValueTypeInRef();
}
}
- 示例图
![引用嵌套值类型.png](https://upload-images.jianshu.io/upload_images/2981616-3a77c91cee879a03.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/240)
-
值类型中嵌套定义引用类型
- 堆栈上将保存该引用类型的引用,而实际的数据则依然保存在托管堆中
- 代码示例
public class TestClass { public int x; public int y; } class Program { //值类型嵌套定义引用类型的情况 public struct NestedRefTypeInValue { //结构体字段,注意,结构中的字段不能被初始化 private TestClass classinValuetype; //结构体中的构造函数,注意,结构体中不能定义无参的构造函数 public NestedRefTypeInValue(TestClass t) { if (t==null) { throw new ArgumentNullException("t"); } classinValuetype = t; classinValuetype.x = 3; classinValuetype.x = 5; } } static void Main(string[] args) { //值类型变量 NestedRefTypeInValue typeInValue = new NestedRefTypeInValue(new TestClass()); } }
- 示例图
两大类型间的转换------装箱与拆箱
-
值类型转换为引用类型称为装箱,引用类型转换为值类型称为拆箱
-
隐式转换;由低级别类型向高级别类型的转换过程(例如子类隐式转换为父类)
-
显式转换(也称为强制类型转换);这种转换可能会导致精度损失或者出现运行时异常;
- 转换格式
(type)(变量,或函数);
-
通过
is
和as
运算符进行安全的类型转换; -
示例从内存角度对装箱,拆箱进行深入分析
- 代码示例
int i=3; //装箱 object o=i; //拆箱 int y=(int)o;
- 装箱步骤:
- 1,内存分配:在托管堆中分配好内存空间以存放复制的实际数据;
- 2,完成实际数据的复制:将值类型实例的实际数据复制到新分配的内存中
- 3,地址返回:将托管堆中的对象地址返回给引用类型的变量
- 装箱示例图
- 拆箱步骤:
- 1,检查实例:首先检查要进行拆箱操作的引用类型变量是否为
null
,如果为null
则抛出异常;反之则检查变量是否和拆箱后的类型是同一类型,若为否,会导致InvalidCasetException
异常; - 2,地址返回:返回已装箱的实际数据部分的地址
- 3,数据复制:将托管堆中的实际数据复制到栈中
- 1,检查实例:首先检查要进行拆箱操作的引用类型变量是否为
- 拆箱示例图
参数传递问题剖析
参数可分为形参和实参两种.形参指的是被调用方法中的参数,也就是方法中定义的参数;实参指的是调用方法时,传递给对应参数的值;
static void Main(string[] args)
{
int addNum=1;
//addNum 就是实参
Add(addNum);
}
//addnum就是形参
private static void Add(int addnum)
{
........
}
- 值类型参数的按值传递
- 传递的是该值类型实例的一个副本,因此方法中对参数的改变不会影响到实参
- 引用类型参数的按值传递
- 当传递的参数是引用类型时,传递和操作的目标是指向对象的地址,而传递的内容是对象地址的复制.由于地址指向的是实参的值,方法对地址进行操作时,实际上操作了地址所指向的值,所以调用方法后原来实参的值就会被修改
string
具有不可变性,因此不会改变; 这是一特殊情况
- 值类型,引用类型参数的按引用传递
- 使用
ref
,out
关键字来实现参数的传递都是引用传递
- 使用