C# 中的值类型和引用类型
数据的类型定义了存储数据需要的内存大小及组成该类型的数据成员。类型还决定了对象在内存中的存储位置——栈或堆。
类型被分为两种:值类型和引用类型,这两种类型的对象在内存中的存储方式不同。
- 值类型只需要一段单独的内存,用于存储实际的数据。
- 引用类型需要两段内存。
- 第一段存储实际的数据,它总是位于堆中。
- 第二段是一个引用,指向数据在堆中的存放位置。
下图展示了每种类型的单个数据项是如何存储的。对于值类型,数据存放在栈里。对于引用类型,实际数据存放在堆里而引用存放在栈里。
值类型
所有值类型都隐式派生自 system.valuetype
,下表显示 c# 值类型:
值类型 | 类别 | 类型后缀 |
---|---|---|
bool |
boolean |
|
byte |
无符号、数字、整型 | |
char |
无符号、数字、整型 | |
decimal |
数字、浮点 |
m 或 m
|
double |
数字、浮点 |
d 或 d
|
enum |
枚举 | |
float |
数字、浮点 |
f 或 f
|
int |
带符号、数字、整型 | |
long |
带符号、数字、整型 |
l 或 l
|
sbyte |
带符号、数字、整型 | |
short |
带符号、数字、整型 | |
struct |
用户定义的结构 | |
uint |
无符号、数字、整型 |
u 或 u
|
ulong |
无符号、数字、整型 |
ul 、ul 、ul 、ul 、lu 、lu 、lu 或 lu
|
ushort |
无符号、数字、整型 |
值类型直接包含值,换言之,变量引用的位置就是内存中实际存储值的位置。
因此,将一个值赋给变量 1,再将变量 1 赋给变量 2,会在变量 2 的位置创建值的拷贝,而不是引用变量 1 的位置。
这进一步造成更改变量 1 的值不会影响变量 2 的值。
下图对此进行了演示。number1
引用内存中的特定位置,该位置包含值 42
。将 number1
的值赋给 number2
之后,两个变量都包含值 42
。但修改其中任何一个值都不会影响另一个值。
类似地,将值类型的实例传给 console. writeline()
这样的方法也会生成内存拷贝。在方法内部对参数值进行的任何修改都不会影响调用函数中的原始值。
引用类型
引用类型的变量存储对数据存储位置的引用,而不是直接存储数据。要去那个位置才能找到真正的数据。所以为了访问数据,“运行时” 要先从变量中读取内存位置,再“跳转”到包含数据的内存位置。
为引用类型的变量分配实际数据的内存区域称为堆(heap)。
由于引用类型只拷贝对数据的引用,所以两个不同的变量可引用相同的数据。因此,对一个变量执行的操作会影响另一个变量所引用的对象。无论赋值还是方法调用都会如此。因此,如果在方法内部更改引用类型的数据,方法执行完成之后,将看到更改后的结果。
总结
一个类型要么是值类型,要么是引用类型。区别在于数据存储的方式:对于值类型,数据存放在栈里。对于引用类型,实际数据存放在堆里而引用存放在栈里。
引用类型的变量存储对其数据(对象)的引用,而值类型的变量直接包含其数据。 对于引用类型,两个变量可引用同一对象;因此,对一个变量执行的操作会影响另一个变量所引用的对象。 对于值类型,每个变量都具有其自己的数据副本,对一个变量执行的操作不会影响另一个变量。
“运行时”的内容请参考这篇文章:
下一篇: C#线程学习笔记七:Task详细用法