栈和堆
(栈的内存是自动释放的,堆内存是.NET中会由GC来自动释放)
栈(Stack)
- 栈是一种只能先进后出的内存结构。
- 存放函数的参数、局部变量、返回数据等值,由编译器自动释放
- 栈只能在一端对数据进行操作,也就是栈顶端进行操作。
- 栈也是一种内存自我管理的结构,压栈自动分配内存,出栈自动清空所占内存
- 栈中的内存不能动态请求,只能为大小确定的数据分配内存,灵活性不高,但是栈的执行效率很高
- 栈的可用空间并不大,所以我们在操作分配到栈上的数据时要注意数据的大小带来的影响
堆(Heap)
- 在c里面叫堆,在c#里面其实叫托管堆。托管堆不同于堆,它是由CLR(公共语言运行库(Common Language Runtime))管理,当堆中满了之后,会自动清理堆中的垃圾。所以,做为.net开发,我们不需要关心内存释放的问题。
- 相比栈只能在一端操作,堆中的数据可以随意存取。
- 能存储大量数据,而且堆能够动态分配存储空间
- 但堆的结构使得堆的执行效率不如栈高,而且不能自动回收使用过的对象
概念
值类型直接存储其值,而引用类型存储对其值的引用
- 值类型派生自 System.ValueType,System.ValueType又派生自System.Objcet
- 引用类型派生自 System.Objcet
值类型:
byte,short,int,long,float,double,decimal,char,bool 和 struct 统称为值类型。
(decimal 关键字指示 128 位数据类型。 与浮点型相比,decimal 类型具有更高的精度和更小的范围,这使它适合于财务和货币计算。)
引用类型:
string 和 class、interface、delegate 、Dynamic 、Object 、数组统称为引用类型。
数组(派生于System.Array)
值类型特点
-
值类型变量声明后,不管是否已经赋值,编译器为其分配内存。
-
值类型的实例通常是在线程栈上分配的(静态分配),但是在某些情形下可以存储在堆中。例如:数组中的元素,引用类型中的值类型的字段,迭代器中的局部变量、闭包情况下匿名函数(lamda)中的局部变量。这些如果值类型分配在线程栈上,有可能出现线程栈中的方法已经调用结束,但是还会访问这些值的情况,可能随着被调用方法的返回而被清除掉。
引用类型特点
- 引用类型当声明一个类时,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其分配堆上的内存空间。当使用 new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。
- 引用类型的对象总是在进程堆中分配(动态分配)。
例子
实际上,对于数组:
TestType[] testTypes = new TestType[100];
如果TestType是值类型,则会一次在托管堆上为100个值类型的元素分配存储空间,并自动初始化这100个元素,将这100个元素存储到这块内存里。
如果TestType是引用类型,则会先在托管堆为testTypes分配一次空间,并且这时不会自动初始化任何元素(即testTypes[i]均为null)。等到以后有代码初始化某个元素的时候,这个引用类型元素的存储空间才会被分配在托管堆上。
区别
相同点:
-
引用类型可以实现接口,值类型当中的结构体也可以实现接口;
-
引用类型和值类型都继承自System.Object类。
1)范围方面
-
C#的值类型包括:结构体(数值类型、bool型、用户定义的结构体),枚举,可空类型。
-
C#的引用类型包括:数组,用户定义的类、接口、委托,object,字符串。
2)内存分配方面:
-
数组的元素不管是引用类型还是值类型,都存储在托管堆上。
-
引用类型在栈中存储一个引用,其实际的存储位置位于托管堆。简称引用类型部署在托管推上。而值类型总是分配在它声明的地方:作为字段时,跟随其所属的变量(实 例)存储;作为局部变量时,存储在栈上。(栈的内存是自动释放的,堆内存是.NET中会由GC来自动释放)
3)适用场合
-
值类型在内存管理方面具有更好的效率,并且不支持多态,适合用做存储数据的载体;引用类型支持多态,适合用于定义应用程序的行为。
-
引用类型可以派生出新的类型,而值类型不能,因为所有的值类型都是密封(seal)的;
-
引用类型可以包含null值,值类型不能(可空类型功能允许将 null 赋给值类型,如 int? a = null; );
-
引用类型变量的赋值只复制对对象的引用,而不复制对象本身。而将一个值类型变量赋给另一个值类型变量时,将复制包含的值。
-
值得注意的是,引用类型和值类型都继承自System.Object类。不同的是,几乎所有的引用类型都直接从System.Object继承,而值类型则继承其子类,即 直接继承System.ValueType。即System.ValueType本身是一个类类型,而不是值类型。其关键在于ValueType重写了Equals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较
using System;
using System.Collections.Generic;
namespace Lesson24_值和引用
{
class Test
{
public static int TestI = 0;
int b = 0;
string str = "123";
TestStrict ts = new TestStrict();
public void Fun()
{
b = 1;
}
}
struct TestStrict
{
public Test t;
public int i;
}
class Program
{
static int b;
static void Main(string[] args)
{
Console.WriteLine("值和引用");
#region 知识回顾
//值类型
//无符号:byte,ushort,uint,ulong
//有符号:sbyte,short,int,long
//浮点数:float,double,decimal
//特殊:char,bool
//枚举:enum
//结构体:struct
//引用类型
//string
//数组
//class
//interface
//委托
//值类型和引用类型的本质区别
//值的具体内容存在栈内存上
//引用的具体内容存在堆内存上
#endregion
#region 问题一 如何判断 值类型和引用类型
//F12进到类型的内部去查看
//是class就是引用
//是struct就是值
int i = 12;
string str = "123";
#endregion
#region 问题二 语句块
//命名空间
// ↓
//类、接口、结构体
// ↓
//函数、属性、索引器、运算符重载等(类、接口、结构体)
// ↓
//条件分支、循环
//上层语句块:类、结构体
//中层语句块:函数
//底层的语句块: 条件分支 循环等
//我们的逻辑代码写在哪里?
//函数、条件分支、循环-中底层语句块中
//我们的变量可以申明在哪里?
//上、中、底都能申明变量
//上层语句块中:成员变量
//中、底层语句块中:临时变量
#endregion
#region 问题三 变量的生命周期
//编程时大部分都是 临时变量
//在中底层申明的临时变量(函数、条件分支、循环语句块等)
//语句块执行结束
//没有被记录的对象将被回收或变成垃圾
//值类型:被系统自动回收
//引用类型:栈上用于存地址的房间被系统自动回收,堆中具体内容变成垃圾,待下次GC回收
int i2 = 1;
string str2 = "123";
//{
// int b = 1;
//}
//Console.WriteLine(b);
//while(true)
//{
// int index = 1;
//}
//想要不被回收或者不变垃圾
//必须将其记录下来
//如何记录?
//在更高层级记录或者
//使用静态全局变量记录
b = 0;
if(true)
{
b = 1;
}
int c = 10;
Test.TestI = c;
//Game g = new Game();
//while(true)
//{
//}
#endregion
#region 问题四 结构体中的值和引用
//结构体本身是值类型
//前提:该结构体没有做为其它类的成员
//在结构体中的值,栈中存储值具体的内容
//在结构体中的引用,堆中存储引用具体的内容
//引用类型始终存储在堆中
//真正通过结构体使用其中引用类型时只是顺藤摸瓜
TestStrict ts = new TestStrict();
#endregion
#region 问题五 类中的值和引用
//类本身是引用类型
//在类中的值,堆中存储具体的值
//在类中的引用,堆中存储具体的值
//值类型跟着大哥走,引用类型一根筋
Test t = new Test();
#endregion
#region 问题六 数组中的存储规则
//数组本身是引用类型
//值类型数组,堆中房间存具体内容
//引用类型数组,堆中房间存地址
int[] arrayInt = new int[5];
object[] objs = new object[5];
#endregion
#region 问题七 结构体继承接口
//利用里氏替换原则,用接口容器装载结构体存在装箱拆箱
TestStruct obj1 = new TestStruct();
obj1.Value = 1;
Console.WriteLine(obj1.Value);
TestStruct obj2 = obj1;
obj2.Value = 2;
Console.WriteLine(obj1.Value);
Console.WriteLine(obj2.Value);
ITest iObj1 = obj1;//装箱 value 1
ITest iObj2 = iObj1;
iObj2.Value = 99;
Console.WriteLine(iObj1.Value);
Console.WriteLine(iObj2.Value);
TestStruct obj3 = (TestStruct)iObj1;//拆箱
#endregion
}
}
interface ITest
{
int Value
{
get;
set;
}
}
struct TestStruct : ITest
{
int value;
public int Value
{
get
{
return value;
}
set
{
this.value = value;
}
}
}
}