
using System; namespace _3_StudyStackAndHeap { /// summary /// 【值类型演示】自定义结构体 PointStruct /// struct 是值类型所有字段数据直接存储在栈内存不使用堆 /// 赋值时完整拷贝全部字段数据多个变量互相独立互不干扰 /// /summary struct PointStruct { public int X; public int Y; /// summary /// 结构体构造函数给字段初始化 /// /summary /// param namexX坐标/param /// param nameyY坐标/param public PointStruct(int x, int y) { X x; Y y; } /// summary /// 打印结构体信息 /// /summary public void ShowInfo() { Console.WriteLine($struct结构体 PointX{X}, Y{Y}); } } /// summary /// 【引用类型演示】Person类 class /// class属于引用类型new出来的真实对象存放在堆内存 /// 栈上的变量仅仅保存堆对象的内存地址引用 /// /summary class Person { public string _name; public int _age; /// summary /// 实例构造函数new的时候自动调用在堆上创建对象 /// /summary /// param namename姓名/param /// param nameage年龄/param public Person(string name, int age) { _name name; _age age; } /// summary /// 打印Person对象信息 /// /summary public void ShowInfo() { Console.WriteLine($Person类对象姓名{_name}年龄{_age}); } } class Program { static void Main(string[] args) { Console.WriteLine(【模块1引用类型 class 栈堆基础演示】); // new Person在堆内存开辟空间创建真实对象栈上p1保存堆对象的内存地址 Person p1 new Person(Tom, 7); Console.WriteLine(执行 Person p1 new Person(\Tom\, 7);); p1.ShowInfo(); // ⭐引用类型赋值仅仅复制栈上保存的堆地址不会在堆新建对象 Person p2 p1; Console.WriteLine(\n执行 Person p2 p1; 【只复制地址堆不会产生新对象】); Console.WriteLine(执行 p2._name \Jerry\; 修改堆对象内部字段); // ⚠重点修改的是堆里面对象的数据栈上p1、p2存储的地址编号本身完全不变 p2._name Jerry; Console.Write(p1 的输出); p1.ShowInfo(); Console.Write(p2 的输出); p2.ShowInfo(); Console.WriteLine(小结p1、p2栈中地址不变共用同一个堆对象修改堆对象字段两边同时变化\n); Console.WriteLine(【模块2引用类型易错点重新new更换栈变量保存的地址】); Console.WriteLine(执行 p2 new Person(\Alice\,10);); // new 在堆创建全新对象修改栈变量p2里面存储的地址p1与p2彻底分家 p2 new Person(Alice, 10); Console.Write(p1 的输出); p1.ShowInfo(); Console.Write(p2 的输出); p2.ShowInfo(); Console.WriteLine(小结只有重新new赋值才会修改栈变量保存的堆地址两者不再关联\n); Console.WriteLine(【模块3值类型 struct 栈内存演示】); // struct 值类型全部数据直接存栈不碰堆内存 PointStruct s1 new PointStruct(10, 20); Console.WriteLine(执行 PointStruct s1 new PointStruct(10, 20);); s1.ShowInfo(); // ⭐值类型赋值栈内存完整拷贝全部字段生成一份独立副本 PointStruct s2 s1; Console.WriteLine(\n执行 PointStruct s2 s1; 栈完整拷贝全部数据); Console.WriteLine(执行 s2.X 999; 修改副本s2的数据); s2.X 999; Console.Write(s1 的输出); s1.ShowInfo(); Console.Write(s2 的输出); s2.ShowInfo(); Console.WriteLine(小结s1、s2是栈上两块独立数据修改s2完全不会影响s1\n); Console.WriteLine(【模块4方法参数传递 栈堆行为演示】); Console.WriteLine(-------4.1 值类型作为参数传入数据副本方法内部修改不影响外部-------); PointStruct st new PointStruct(1, 2); Console.WriteLine(调用方法前外部st); st.ShowInfo(); ModifyStruct(st); Console.WriteLine(调用方法结束后外部st); st.ShowInfo(); Console.WriteLine(小结传入的是栈副本方法改副本外部原始数据不受影响\n); Console.WriteLine(-------4.2 引用类型作为参数传入地址副本修改堆对象字段会影响外部-------); Person per new Person(张三, 18); Console.WriteLine(调用方法前外部per); per.ShowInfo(); ModifyPersonField(per); Console.WriteLine(调用方法结束后外部per); per.ShowInfo(); Console.WriteLine(小结形参拿到堆地址副本修改堆对象字段外部可以看到变化\n); Console.WriteLine(-------4.3【高频大坑】方法内部给形参new对象不会改变外部变量-------); Person pTest new Person(原始数据, 20); Console.WriteLine(调用方法前外部pTest); pTest.ShowInfo(); ModifyPersonNew(pTest); Console.WriteLine(调用方法结束后外部pTest); pTest.ShowInfo(); Console.WriteLine(小结p new Person()仅仅修改局部形参p栈上的地址外面变量完全不受影响\n); Console.WriteLine(【模块5null 空引用演示引用类型专属】); Person pNull null; Console.WriteLine($Person pNull null; pNull null → {pNull null}); Console.WriteLine(null含义栈上引用变量不保存任何堆地址不指向堆的任何对象); // pNull.ShowInfo(); // ⚠取消注释运行报运行时异常 NullReferenceException 空引用异常 // ❌值类型不允许赋值null PointStruct sNull null; 直接编译报错 Console.WriteLine(\n【模块6string特殊引用类型演示】); string strA 你好; string strB strA; strB 世界; Console.WriteLine($strA{strA} strB{strB}); Console.WriteLine(string是引用类型但是字符串不可变赋值会生成新字符串对象表现类似值类型); Console.ReadKey(); } /// summary /// 修改值类型结构体参数 /// 值类型传入栈数据副本方法内部修改只改动副本 /// /summary /// param names结构体参数副本/param static void ModifyStruct(PointStruct s) { s.X 888; Console.WriteLine(----方法内部修改之后的参数s----); s.ShowInfo(); } /// summary /// 修改引用类型对象的字段 /// 传入堆地址副本形参与外部变量指向同一个堆对象 /// 修改堆对象字段外部可以感知变化 /// /summary /// param namepPerson引用参数/param static void ModifyPersonField(Person p) { p._name 被方法修改; Console.WriteLine(----方法内部修改对象字段之后p----); p.ShowInfo(); } /// summary /// ⚠易错坑演示方法内部给引用形参new新对象 /// 仅仅修改局部形参p栈上存储的地址外部变量完全不受影响 /// /summary /// param namepPerson引用参数/param static void ModifyPersonNew(Person p) { // 局部p指向堆上新对象仅仅改变局部栈变量p p new Person(方法内部全新对象, 99); Console.WriteLine(----方法内部new之后局部参数p----); p.ShowInfo(); } } }栈与堆、值类型 引用类型综合知识点总结 基于上面示例代码整理逐条记录一、内存两块区域栈 Stack、堆 Heap栈 Stack空间小、读写速度快。存放局部变量、方法参数。方法执行完毕自动回收栈内存。引用类型的栈变量只存放堆对象的内存地址引用编号不存储对象真实字段数据。值类型的栈变量直接存放完整的全部字段数据。堆 Heap存储空间大。执行new class()时在堆存放对象真实字段数据由 GC 垃圾回收自动清理不再使用的堆对象。struct 结构体属于值类型默认数据全部保存在栈不占用堆内存。二、class 类引用类型内存行为Person p1 new Person(Tom, 7); Person p2 p1; p2._name Jerry;new Person()在堆内存创建对象保存_name、_age真实数据返回堆内存地址。栈变量p1保存堆地址栈里面不会存放_name、_age。Person p2 p1;栈上新变量 p2 复制 p1 里面的堆地址。堆不会创建新对象。p2._name Jerry;✅栈上 p1、p2 存储的地址编号完全不变栈变量本身没有改动✅ 修改的是堆内存里面对象的字段数据✅ p1、p2 拿着同一个堆地址读取数据两者读取到修改之后的值。p2 new Person(Alice, 10);new 在堆开辟全新对象修改栈变量 p2 保存的堆地址编号。p1、p2 彻底分开互不影响。三、struct 结构体值类型内存行为PointStruct s1 new PointStruct(10, 20); PointStruct s2 s1; s2.X 999;struct 全部字段数据直接存储在栈内存不使用堆。s2 s1栈内存完整拷贝全部字段s1、s2 是两块互相独立的栈内存。修改s2.X只会修改 s2 这份栈数据s1 完全不受影响。值类型不可以赋值 null直接编译报错。四、方法参数传递栈堆行为值类型作为参数传入栈数据的副本。方法内部修改副本外部原始变量完全不受影响。引用类型作为参数传入堆地址的副本。形参和外部变量指向同一个堆对象。如果修改p._name修改堆对象字段外部变量可以看到变化。⚠️ 高频大坑方法内部写p new Person()仅仅修改局部形参 p 栈上保存的地址外部变量完全不受任何改变。仅仅局部参数换了指向。五、null 空引用Person pNull null;null栈上引用变量不保存任何堆地址不指向堆中任何对象。null 的变量调用对象方法/字段运行抛出NullReferenceException空引用异常属于运行时错误不是编译错误。struct 值类型不能赋值 null编译直接报错。六、string 特殊引用类型string 属于引用类型对象存放在堆但是字符串具备不可变性。修改字符串变量会生成全新的堆字符串对象表现行为看起来和值类型一致。七、核心记忆对照表代码对象存储位置赋值行为参数传递能否为 nullclass 类引用类型栈存地址堆存真实对象复制堆地址多个变量共享同一个堆对象传地址副本改字段会影响外部方法内重新 new 只改变局部参数✅ 可以 nullstruct 结构体值类型栈存完整全部数据完整拷贝数据副本变量互相独立传数据副本方法内修改副本不影响外部❌ 不可以 null八、高频编译 运行错误点总结修改引用类型对象字段栈变量保存的地址不会发生变化只有执行变量 new Xxx()栈变量的地址才会被替换。方法内部对引用形参执行p new Xxx()不会改变外部变量这是面试高频易错坑。null 引用变量调用成员运行时报NullReferenceException不是编译报错。struct 值类型写变量 null直接编译报错。区分概念引用变量存的是门牌号地址堆才是真正的房子对象修改房子内部物品不会涂改纸条上门牌号。九、通俗口诀引用类型栈存门牌号堆中才是真对象修改对象字段只改堆不动栈上门牌号只有重新 new 赋值栈里面地址才换掉。值类型全部放栈上赋值拷贝全数据两者互不干扰。引用参数改字段外部跟着变参数内部做 new外部看不见。null 就是没有门牌号调用成员直接报错。