C# 内存机制详解:值类型、引用类型与 String 的不可变性
1. 值类型 (Value Type)
代表类型: int, bool, struct, double, char 等。
特点
- 存储位置: 变量名和实际数据都保存在 栈 (Stack) 中。
- 赋值行为: 直接拷贝一份完整的数据。修改其中一个,另一个不受影响。
代码示例
csharp
int a = 10;
int b = a; // 拷贝具体的值
b = 20; // 修改 b,a 依然是 10
内存图解
text
栈 (Stack) 堆 (Heap)
+----------+----------+ +-------------------+
| 变量名 | 数据 | | |
+----------+----------+ | |
| a | 10 | | (值类型不占用堆) |
+----------+----------+ | |
| b | 20 | | |
+----------+----------+ +-------------------+
2. 引用类型 (Reference Type)
代表类型: class, interface, array, delegate 等。
特点
- 存储位置: 栈中存的是 内存地址 (指针) ,实际的对象内容存在 堆 (Heap) 中。
- 赋值行为: 拷贝的是地址。两个变量指向同一个堆对象。
代码示例
csharp
Person p1 = new Person { Name = "Alice" };
Person p2 = p1; // 拷贝地址,两人指向同一个对象
p2.Name = "Bob"; // 通过 p2 修改堆内容,p1.Name 也会变成 "Bob"
内存图解
text
栈 (Stack) 堆 (Heap)
+----------+-----------+ +--------------------------+
| 变量名 | 内存地址 | | 对象实际数据 |
+----------+-----------+ +--------------------------+
| p1 | [0x001] | --------> | 地址[0x001]: {Name:"Bob"} |
+----------+-----------+ / +--------------------------+
| p2 | [0x001] | ----/ (p1和p2共享同一块堆空间)
+----------+-----------+
3. String 的特殊性:不可变的引用类型
string 是引用类型,但在行为上表现得像值类型,这就是因为它的 不可变性 (Immutability)。
核心规则
一旦在堆上创建了字符串对象,它的内容就永远不能被修改。 所有的"修改"操作实际上都是在堆中创建了一个全新的字符串对象,并让变量指向新地址。
代码演示与内存步解
csharp
string s1 = "Hi";
string s2 = s1; // s2 指向与 s1 相同的地址
s2 = "Yo"; // 并不是修改 "Hi",而是新开辟空间存 "Yo"
第一步:执行 s1 = "Hi"; s2 = s1;
text
栈 (Stack) 堆 (Heap)
+----------+-----------+ +---------------------+
| 变量名 | 内存地址 | | 字符串对象 |
+----------+-----------+ +---------------------+
| s1 | [0xAAA] | --------> | [0xAAA]: "Hi" |
+----------+-----------+ / +---------------------+
| s2 | [0xAAA] | ----/
+----------+-----------+
第二步:执行 s2 = "Yo";
text
栈 (Stack) 堆 (Heap)
+----------+-----------+ +---------------------+
| 变量名 | 内存地址 | | 字符串对象 |
+----------+-----------+ +---------------------+
| s1 | [0xAAA] | --------> | [0xAAA]: "Hi" |
+----------+-----------+ +---------------------+
| s2 | [0xBBB] | --------> | [0xBBB]: "Yo" |
+----------+-----------+ +---------------------+
4. 总结对比表
| 特性 | 值类型 (Value Type) | 引用类型 (Reference Type) | String (特殊引用类型) |
|---|---|---|---|
| 存储位置 | 栈 (Stack) | 栈(地址) + 堆(对象) | 栈(地址) + 堆(对象) |
| 赋值行为 | 拷贝具体数值 | 拷贝内存地址 | 拷贝内存地址 |
| 修改行为 | 原地修改变量值 | 原地修改堆中的对象内容 | 禁止原地修改,创建新对象并更向新地址 |
| 典型代表 | int, struct |
class, array |
string |
为什么 String 要这样设计?
- 性能与内存: 支持"字符串驻留",相同内容的字符串在内存中只存一份。
- 安全: 字符串作为参数传递(如文件路径)时,不会在途中被意外修改。
- 线程安全: 多个线程同时读一个不可变对象,不需要加锁。
提示:在进行大量字符串拼接操作时,请务必使用 StringBuilder 以避免在堆中产生大量无用的中间字符串碎片。