比较类与结构体:了解何时使用值类型或引用类型、内存与性能的差异,以及用于更好设计的实用规则。
November 15, 2025 (5mo ago) — last updated December 7, 2025 (4mo ago)
类与结构体:何时使用各自
比较类与结构体:了解何时使用值类型或引用类型、内存与性能的差异,以及用于更好设计的实用规则。
← Back to blog
类与结构体:何时使用各自
摘要: 比较类与结构体 —— 值与引用、内存、性能,以及帮助你选择正确类型以提高代码效率的设计指导。
介绍
在类与结构体之间做出决定,更多是语义层面的考虑而非语法。关键问题是你是否需要值语义(复制数据)或引用语义(共享身份)。这种差异会影响内存使用、性能、可变性和架构设计。本指南解释这些权衡并给出选择合适类型的实用规则。
核心区别:值语义 vs 引用语义

当你去掉语言的语法细节,类与结构体的讨论本质上就是值类型与引用类型的对比。把结构体想象成复印的便签本:你把笔记给别人,他们得到自己的一份拷贝。他们可以在自己的拷贝上涂写而不会改变你的原件。这就是值语义——安全、孤立的拷贝。类则像一个共享文档:你发送一个链接,大家编辑的是同一个实时对象。这就是引用语义——共享身份与共享状态。
主要差异一览
| 特性 | 结构体(值类型) | 类(引用类型) |
|---|---|---|
| 数据处理 | 传递时复制数据 | 传递的是引用(指针) |
| 内存分配 | 通常内联或在栈上存放 | 在堆上分配 |
| 生命周期 | 短暂、临时的复制品 | 长期存在、可共享的实例 |
| 身份 | 由数据相等性定义 | 独立于数据的唯一身份 |
| 继承 | 通常不支持继承 | 支持继承和多态 |
| 主要使用场景 | 小而自包含的值 | 具有行为的复杂实体 |
这些原则会对延迟、内存使用和正确性产生实际影响。有意地选择会让你的代码更可预测、更易维护。
内存分配如何影响性能

栈和堆是这项决策大部分性能影响的来源。
栈:快速且可预测
栈是一个后进先出(LIFO)的内存区域,函数局部数据在其中入栈和出栈。在栈上分配非常便宜,因为它只是指针运算。对于小型值类型,分配和释放几乎是免费的。
堆:灵活但代价更高
堆允许对象的生命周期超出单次函数调用,但堆分配更慢,可能触发垃圾回收或需要手动释放。引用类型引入了额外的间接层:栈上保存的是指向堆数据的指针。频繁的堆分配会增加 GC 压力,并可能在托管运行时中引起停顿1。
C# 示例
// Value Type - lives inline (often on the stack)
public struct PointStruct {
public int X;
public int Y;
}
// Reference Type - object lives on the heap
public class PointClass {
public int X;
public int Y;
}
public void ProcessPoints() {
PointStruct p1 = new PointStruct { X = 10, Y = 20 };
PointClass p2 = new PointClass { X = 10, Y = 20 };
}
在紧密循环中,为小对象进行成千上万次的堆分配会显著增加 GC 活动;数组中的结构体通常能获得更好的缓存局部性并降低 GC 压力2。
各语言如何处理类与结构体

不同语言侧重不同的默认行为。最佳选择既取决于原始性能,也取决于语言习惯用法。
C++:关键字几乎相同
在 C++ 中,struct 和 class 几乎相同;唯一的技术性差别是默认访问权限(struct 为 public,class 为 private)。对纯数据聚合使用 struct,对封装类型和复杂行为使用 class3。
C#:明确的值/引用区分
C# 将区分明确化:struct 是真正的值类型,而 class 是引用类型。对小且不可变的值(坐标、颜色)使用结构体,对具有身份和可变共享状态的实体使用类。
Swift:偏好值类型
Swift 倾向于先使用值。当心:Apple 的指导和 Swift 社区默认偏向使用 struct,并仅在需要引用语义(例如共享可变状态或与 Objective-C API 交互)时使用 class4。
Rust:所有权与安全性
Rust 使用 struct 再配合所有权和借用模型,在没有垃圾收集器的情况下提供内存安全。行为通过 impl 块附加,编译器在编译时强制执行所有权和借用规则,从而在运行前防止许多共享状态的错误5。
struct Player {
username: String,
level: u32,
is_active: bool,
}
impl Player {
fn level_up(&mut self) {
self.level += 1;
}
}
Rust 的方法让你在获得直接内存控制的性能的同时,享受编译期的安全保证。
何时选择结构体以获得更好性能
当类型较小、自包含、并且被当作值而非身份来处理时,选择结构体。典型候选:
- 几何点(Point2D)
- 颜色值(RGB/RGBA)
- 小型配置负载
- 轻量级计算输入
好处包括更少的堆分配、更好的缓存局部性,以及在托管运行时中更低的 GC 压力。由于内存访问模式对 CPU 更友好,改进的缓存局部性在数据密集型循环中可以带来显著的速度提升2。
何时选择类以建模复杂行为
当对象具有稳定的身份、共享可变状态,或需要继承或复杂生命周期管理时,选择类。典型候选:
- 用户配置文件或领域实体
- 数据库或网络连接对象
- 协调状态的服务和管理器
类是许多面向对象模式的基础。继承和多态使得建模复杂关系和行为更加容易。
决策清单:结构体 vs 类
| 考量 | 使用结构体(值) | 使用类(引用) |
|---|---|---|
| 身份 | 数据就是身份 | 对象具有唯一身份 |
| 可变性 | 不可变或小型、孤立状态 | 共享的、可变的状态 |
| 行为 | 与数据绑定的简单逻辑 | 复杂交互和行为 |
| 生命周期 | 短暂、局部作用域 | 长期、全局/应用级 |
| 共享 | 复制是安全的 | 必须通过引用共享 |
常见问题
结构体可以有方法吗?
可以。像 C#、Swift 和 Rust 这样的现代语言允许结构体拥有方法、初始化器,以及实现协议或接口。主要区别仍然在于它们如何被复制和传递。
结构体总是更快吗?
不一定。小型结构体通常比堆分配的对象更快,但大型结构体复制开销可能很高。始终进行测量:在对真实工作负载做出重大更改前先进行性能分析。
结构体支持继承吗?
通常不支持。结构体很少支持继承,但许多语言允许结构体实现接口或协议,从而在不依赖深度继承链的情况下实现灵活的组合。
实用问答
问:我应该何时将类重构为结构体?
答:当该类型是小型、不可变且没有唯一身份时,重构为结构体可以减少堆分配并提供更清晰的值语义。
问:在托管语言中如何避免 GC 停顿?
答:通过优先对小值使用结构体、重用对象和使用对象池来减少短生命周期堆分配;在负载下测量 GC 行为以优化1。
问:最简单的经验法则是什么?
答:如果对象表示“一个值”,使用结构体;如果表示“具有身份的事物”,使用类。
三个简明的问答小节
问答 1 — 性能权衡
问:切换到结构体是否总能提升速度? 答:不是。对小且频繁创建的值使用结构体可以减少 GC 压力并改善缓存局部性;避免大型结构体因为复制代价高而带来负面影响。
问答 2 — 安全性与正确性
问:结构体能减少因共享状态引起的错误吗? 答:能。值语义可以防止意外的共享可变性,当值被复制而非共享时,可以减少并发和状态相关的错误。
问答 3 — 设计与架构
问:什么时候类比结构体更合适? 答:当需要身份、长期生命周期或继承与多态时,使用类更合适。
在 Clean Code Guy,我们帮助团队重构以实现可扩展性和可维护性。了解更多请访问 https://cleancodeguy.com。
AI编写代码。您让它持久。
在AI加速的时代,干净代码不仅仅是好的实践 — 它是能够扩展的系统与在自己的重量下崩溃的代码库之间的区别。