类与结构体的区别
本教程共 93 篇 · 第 58 篇 · 更新于 2026-08-08 · 约 9 分钟阅读
本节目标:学完你能说清值类型和引用类型的本质差异,并知道什么场景该用类、什么场景该用结构体。
第 55 章提过,类和结构体能力高度重合,但类多出了继承、类型转换、反初始化、引用计数四项本领。可决定你日常选型的关键,其实藏在更深的一层:结构体是值类型(value type),类是引用类型(reference type)。这一字之差,决定了赋值、传参、比较时的全部行为。
1-1 值类型:赋值即拷贝
值类型的特点是,当它被赋给变量、被当作参数传进函数时,会发生一次完整的拷贝。Swift 里所有基础类型——整数、浮点、布尔、字符串、数组、字典——背后都是结构体,也就是说它们都是值类型。你自己定义的结构体和枚举,自然也是值类型。
struct Resolution {
var width = 0
var height = 0
}
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
cinema.width = 2048
print(hd.width) // 1920,原值没动
print(cinema.width) // 2048
cinema = hd 这一刻,系统把 hd 里的数值整份复制了一份给 cinema。之后改 cinema.width,只是动了那份拷贝,hd 毫发无损。你脑海里可以把它想成”复印文件”:两份互不影响。
枚举也是值类型,行为完全一样。把当前值赋给另一个变量时,拿到的是副本,原值怎么变都不波及它。
1-2 引用类型:赋值即共享
类不一样。赋值或传参时,它不拷贝对象,而是让新变量也指向同一个对象。
class VideoMode {
var frameRate = 0.0
}
let tenEighty = VideoMode()
tenEighty.frameRate = 25.0
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
print(tenEighty.frameRate) // 30.0
alsoTenEighty 和 tenEighty 只是同一个 VideoMode 对象的两个名字。改其中一个,另一个看得到,因为它们背后是同一份数据。这更像”共享一份在线文档的链接”:谁改了,大家都看到新内容。
顺带一提,正因类的实例是共享的,当一个对象在你的代码里被多处引用时,推理”谁改了它”会更费脑。值类型则简单得多:和同一份值打交道的地方都挨在一起,不容易出意外。这也是官方建议优先用结构体的核心原因。
1-3 一个反直觉的细节:let 常量
引用类型带来一个坑:即便用 let 声明常量,也能改它内部的 var 属性。因为 let 锁死的是”引用”(指向哪个对象),不是对象内部。
let fixed = VideoMode()
fixed.frameRate = 60 // 合法,改的是对象内部
而结构体若是被 let 常量持有,连内部的 var 属性都改不了(第 56 章讲过)。记住这条对照,能帮你避开不少编译报错。
1-4 成员初始化器的有无
结构体有个类没有的便利:只要你没自己写初始化器,编译器会自动送你一个成员初始化器(memberwise initializer),可以按名字逐一传参。
let vga = Resolution(width: 640, height: 480)
类可没这待遇。类的实例不会自动获得成员初始化器,你得自己写初始化器来设定属性初值(第 64 章会系统讲)。这种差异源于类有继承链,自动生成的初始化器容易漏掉父类的责任,所以 Swift 把选择权交给你。
1-5 同一性运算符 ===
因为类是引用类型,可能出现多个变量指向同一个实例。有时你想确认”这俩到底是不是同一个对象”,就得用同一性运算符(identity operators):===(完全同一)和 !==(不完全同一)。
if tenEighty === alsoTenEighty {
print("它们指向同一个 VideoMode 实例")
}
务必分清 === 和 ==。=== 问的是”是不是内存里同一个对象”;== 问的是”两个对象的值是否相等”。结构体没这概念,因为赋值时它就拷贝了,永远不存在”两个变量指向同一份”的情况,所以结构体用 == 比较值即可,不能用 ===。要注意:你自己的类型若想支持 ==,得自己去实现相等判断(第 79 章讲 Equatable)。
1-6 和 C 系指针的区别
如果你写过 C/C++/Objective-C,可能会联想到指针。Swift 里指向引用类型实例的常量或变量,确实有点像指针,但它不是直接指向内存地址的那个裸指针,也不用什么星号 * 来声明。它就像普通变量一样写,系统替你管理引用关系。真要直接和内存打交道时,标准库也提供了专门的指针与缓冲区类型,但日常开发基本用不到。
1-7 到底该用哪个
苹果官方给的准则很明确:优先用结构体,因为它好推理、不易错;只有确实需要类独有能力时才用类。具体来说,下面这些情况倾向用类:需要继承复用另一套逻辑、需要在销毁时做清理(deinit)、需要多个地方共享同一份可变状态、需要靠类型转换在运行时判断具体类型。
反之,下面这些用结构体更香:描述一个独立的值(坐标、尺寸、区间)、只是临时装一组数据、不打算被继承、希望赋值后各份数据互不干扰。Swift 标准库里连 String、Array、Dictionary 都选了结构体,可见官方对值类型的偏爱。把”值语义优先”刻进习惯,你的代码会更稳、更好懂。
1-8 值类型与引用类型的性能账
选值类型不只是语义偏好,也有性能考量。值类型(结构体、枚举)在赋值时拷贝,但 Swift 对数组、字典、字符串做了写时复制(copy-on-write)优化:多个副本先共享同一块内存,只有其中一个被修改时才真正拷贝。所以你不用担心”每次赋值都深拷贝一大块数据”拖慢程序。类因为是共享引用,天然没有拷贝成本,但代价是到处都可能改同一份数据,推理更难、还引出循环引用等内存问题。
日常选型可以这样记:默认结构体,简单安全;当你确实需要共享可变状态或用继承,才换类。很多 Swift 老手的项目里,类只占很小一部分。别因为”类听起来更面向对象”就滥用它——Swift 的面向对象更多是通过协议和值类型来表达的。还有个细节:值类型的拷贝是”语义上的拷贝”,从代码角度看就像两份独立数据,至于底层是否真的复制了内存,标准库替你优化好了,你不必操心。这种”看起来像拷贝、必要时才拷贝”的设计,正好兼顾了值语义的安全和性能。