C# 内存机制详解:值类型、引用类型与 String 的不可变性


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 要这样设计?

  1. 性能与内存: 支持"字符串驻留",相同内容的字符串在内存中只存一份。
  2. 安全: 字符串作为参数传递(如文件路径)时,不会在途中被意外修改。
  3. 线程安全: 多个线程同时读一个不可变对象,不需要加锁。

提示:在进行大量字符串拼接操作时,请务必使用 StringBuilder 以避免在堆中产生大量无用的中间字符串碎片。

相关推荐
聊浮游30 分钟前
JAVA2026最新全套学习资料、学习路线
java·开发语言·jvm·mysql·spring·maven·idea
MacroZheng42 分钟前
完美替代 Navicat!这款内置 AI 的数据库工具,太香了!
java·后端·mysql
SamDeepThinking1 小时前
从REST到gRPC,一个API选型的思考框架
java·后端·程序员
Zane19941 小时前
接口都能写默认实现了,为什么还需要抽象类
java·后端
未秃头的程序猿1 小时前
读Spring AI源码的方法——不是硬啃,是带着问题去翻
java·后端·spring
用户3126874877201 小时前
别再 Thread.sleep 硬等了!并发工具类到底怎么选?
java
Java内核笔记1 小时前
万字长文剖析 Spring Boot 4 自动配置机制源码:从 @EnableAutoConfiguration 到条件装配
java·后端
evans在进步2 小时前
Spring MVC 核心机制详解:全局异常处理、请求跳转与数据绑定
java·spring·mvc
weixin_BYSJ19872 小时前
springboot小区物业管理小程序---附源码45555
java·javascript·spring boot·python·django·flask·php
coder_lorraine2 小时前
Halo 2.x 回收站文章无法删除?一篇文章教你彻底解决
java·运维