首页 / Swift 编程语言教程 / 类与结构体的区别

Swift 编程语言教程

类与结构体的区别

本教程共 93 篇 · 第 58 篇 · 更新于 2026-08-08 · 约 9 分钟阅读

SwiftSwift 编程语言教程结构体值类型引用类型成员初始化器同一性

本节目标:学完你能说清值类型和引用类型的本质差异,并知道什么场景该用类、什么场景该用结构体。

第 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

alsoTenEightytenEighty 只是同一个 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 标准库里连 StringArrayDictionary 都选了结构体,可见官方对值类型的偏爱。把”值语义优先”刻进习惯,你的代码会更稳、更好懂。

1-8 值类型与引用类型的性能账

选值类型不只是语义偏好,也有性能考量。值类型(结构体、枚举)在赋值时拷贝,但 Swift 对数组、字典、字符串做了写时复制(copy-on-write)优化:多个副本先共享同一块内存,只有其中一个被修改时才真正拷贝。所以你不用担心”每次赋值都深拷贝一大块数据”拖慢程序。类因为是共享引用,天然没有拷贝成本,但代价是到处都可能改同一份数据,推理更难、还引出循环引用等内存问题。

日常选型可以这样记:默认结构体,简单安全;当你确实需要共享可变状态或用继承,才换类。很多 Swift 老手的项目里,类只占很小一部分。别因为”类听起来更面向对象”就滥用它——Swift 的面向对象更多是通过协议和值类型来表达的。还有个细节:值类型的拷贝是”语义上的拷贝”,从代码角度看就像两份独立数据,至于底层是否真的复制了内存,标准库替你优化好了,你不必操心。这种”看起来像拷贝、必要时才拷贝”的设计,正好兼顾了值语义的安全和性能。