一、基础概念回顾:
值类型变量直接包含值本身,通常分配在栈(Stack)内存中。
基本数据类型:
int
,float
,char
,bool
,enum
自定义结构体
struct
引用类型(Reference Type)
引用类型变量包含的是指向实际对象的引用地址,实际数据位于堆(Heap)内存中。
string
(虽然看起来像值,但本质是引用类型)数组、类
class
接口
interface
、委托delegate
📌 结构体(
struct
)是值类型,类(class
)是引用类型。
可以参考下面这篇博客
秒懂 栈内存和堆内存(深入底层)-CSDN博客
二、如何判断类型是值类型还是引用类型
int i = 12; // struct,值类型 string str = "123"; // class,引用类型(不可变)
快捷判断方法:
F12
查看类型定义,是struct
就是值类型,是class
就是引用类型。
内存类型 存储的常见数据类型 / 数据 特点说明 栈内存 基本数据类型(如 int、float、boolean、char 等) 占用空间小、大小固定,由系统自动分配和释放,访问速度快 栈内存 引用数据类型的引用(如对象的地址、数组的地址) 存储的是指向堆内存中对象的地址,而非对象本身,随作用域结束自动释放 堆内存 引用数据类型的实例(如对象、数组、字符串常量池以外的字符串) 占用空间可动态变化,由程序员通过代码(如 new)申请,一般需手动释放(或由垃圾回收机制处理),访问速度相对较慢
注:字符串的存储较为特殊,字符串常量通常存放在字符串常量池中(属于方法区,非堆内存,但有些实现中会将其归为堆的一部分),而通过new
创建的字符串对象则存放在堆内存中。
三、变量生命周期
临时变量
int i2 = 1; string str2 = "123";
定义在方法、if、while等语句块中,生命周期只在语句块内。
值类型:变量消失,值直接被系统自动销毁。
引用类型:引用被销毁,堆上的对象不会立刻销毁,但会变成垃圾对象,等待 GC 回收。
int c = 10; Test.TestI = c; // 静态变量,全局存在,不会自动销毁
想保留变量生命周期?
定义为类字段/静态字段
或存在更高层作用域中
四、结构体中值和引用
TestStrict ts = new TestStrict();
TestStrict
是一个结构体(值类型),其内部含有:
Test t
:引用类型成员,指向堆上数据
int i
:值类型成员,值存在栈上结构体中引用类型的成员依旧存堆上,只是结构体本身在栈中,保存着引用的地址。
C语言中结构体 自引用 和 相互引用_c语言结构体指针自引用-CSDN博客
五、类中值和引用
Test t = new Test();
Test
是类,属于引用类型,变量t
指向堆内存。类中的字段
b
、str
、ts
都存储在堆内存中。引用类型的值类型成员也会随对象一同存储在堆中。
六、数组的内存存储规则
int[] arrayInt = new int[5]; // 值类型数组 object[] objs = new object[5]; // 引用类型数组
数组类型 内容存储位置 int[]
每个元素都直接存堆中的具体值 object[]
每个元素存的是引用,指向其他堆上对象
七、结构体实现接口:装箱与拆箱
ITest iObj1 = obj1; // 装箱 iObj2.Value = 99; TestStruct obj3 = (TestStruct)iObj1; // 拆箱
装箱(Boxing):
把值类型包装成引用类型(如接口、object);
会在堆上新建一个副本,不会影响原始值。
拆箱(Unboxing):
从引用类型中提取出原始的值类型对象。
关键点:结构体装箱会产生性能开销,赋值后修改不会影响原值!
输出分析:obj1.Value = 1; obj2.Value = 2; Console.WriteLine(obj1.Value); // 1(说明 obj2 是 obj1 的拷贝) iObj1 = obj1; iObj2 = iObj1; iObj2.Value = 99; Console.WriteLine(iObj1.Value); // 1(修改无效,是装箱后的副本)
八、GC(垃圾回收) 在这段代码中的体现
虽然你没有显式调用
GC.Collect()
,但这段代码隐含地体现了 GC 的触发场景:
当结构体中的引用成员失效(如 Test 类对象没被引用);
当函数结束,临时变量的栈空间释放;
此时引用类型实例变为“孤岛”,等待 GC。
回顾GC的触发逻辑:
引用类型对象失去所有引用后,进入垃圾状态。
GC 会在内存压力大时自动触发(或手动调用
GC.Collect()
)。