类与对象
本教程共 93 篇 · 第 55 篇 · 更新于 2026-08-08 · 约 8 分钟阅读
本节目标:学完你能用 class 定义类、用初始化语法创建对象,并理解类作为引用类型在赋值和传递时的行为。
类(class)是 Swift 组织代码的基石之一。它把数据(属性)和操作这些数据的行为(方法)打包成一个整体,让你用更接近真实世界的方式去描述事物。你前面学过结构体(struct),类和结构体有很多相似之处,但类有一个最关键的身份特征:它是引用类型。这一章我们先把类的基本用法讲清楚,引用语义的深层对比留到第 58 章专门拆解。
1-1 类和对象到底是什么关系
很多初学者会被这两个词绕晕。简单说,类是一张图纸,对象(在 Swift 里更常叫实例,instance)是按图纸盖出来的房子。你先定义好一个类,描述它长什么样、能干什么;然后可以照着它创建很多个独立的对象。
在 Swift 官方书里,类的实例传统上叫 object(对象)。但因为 Swift 的结构体和类功能非常接近,很多语法对两者都通用,所以官方书更习惯用 instance(实例)这个更中性的词。你只要心里明白:平时说的”对象”和”实例”在这里是一回事。
类的最大价值在于封装。比如你要描述一台电视机,它的品牌、尺寸、音量就是数据,换台、调音量就是行为。把这些都放进一个 Television 类里,外面用起来就只看到一个清晰的接口,不用关心内部怎么实现。
1-2 用 class 关键字定义类
定义类的语法和结构体几乎一样,只是把 struct 换成 class,类型名用大驼峰(UpperCamelCase):
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
这段代码里出现了几个要点。第一,类型名 VideoMode 首字母大写,这是 Swift 的约定,和 String、Int 这类标准类型保持一致。第二,属性名 resolution、frameRate 用小驼峰(lowerCamelCase),用来和类型名区分开。第三,name 是可选类型 String?,它一开始没有值,会自动被赋予 nil,表示”暂时还没名字”。
和很多其他语言不同,Swift 不需要你为类单独写头文件和实现文件。你在同一个文件里把类写完,别人就能直接用。编译器会自动帮你生成对外的接口,这点对新手非常友好。
1-3 什么是实例化
光有类的定义还不够,它只是图纸。要真正用起来,得创建实例。这个过程叫实例化。
Swift 用初始化语法创建实例:写出类型名,后面跟一对空括号。
let someVideoMode = VideoMode()
这里的 VideoMode() 就是调用了初始化器。它会生成一个全新的 VideoMode 对象,里面的属性都被设成默认值。比如 frameRate 是 0.0,interlaced 是 false,name 是 nil。类是怎么保证每个属性都有初始值的、那些空括号背后到底发生了什么,我们在第 64 章初始化器里详细讲。这一章你先记住:要造对象,就写 类型名()。
1-4 用点语法访问属性和方法
创建好实例后,用点语法(dot syntax)访问它的成员。点写在实例名和成员名之间,中间没有空格:
print("宽度是 \(someVideoMode.resolution.width)")
someVideoMode.resolution.width = 1280
someVideoMode.frameRate = 25.0
someVideoMode.name = "1080i"
点语法可以一层层往下钻。上面第一行先拿到 someVideoMode 的 resolution 属性,再拿它的 width。第三行直接给 frameRate 赋了新值。注意只有用 var 声明的属性才能改,用 let 声明的常量属性在实例创建后就不能变了。
访问属性、修改属性,本质上都是通过点语法。方法(也就是类里的函数)也是同样的点语法调用,这一点和第 59 章会专门展开。
1-5 类和结构体有哪些共同点
Swift 刻意让结构体和类承担相似的能力,所以你定义的类能做的事,结构体基本也能做。两者都能:定义存储属性的变量、定义提供功能的方法、用下标语法访问值、写初始化器设置初始状态、通过扩展增加新功能、遵循协议获得标准能力。
正因为能力这么像,初学者常纠结”到底该用哪个”。记住一条总原则:优先用结构体,因为它的值语义更简单、更容易推理;只有当确实需要类独有的能力(比如继承、引用计数)时,才用类。在苹果官方的工程实践里,绝大多数自定义类型其实都是结构体或枚举。
1-6 类独有的四项能力
结构体做不到、只有类才拥有的能力有四条,这也是类存在的理由:
第一是继承(inheritance)。一个类可以继承另一个类的属性、方法和下标,并在此基础上扩展或改写。结构体不能继承,这是类区别于结构体的根本特性。
第二是类型转换(type casting)。你可以在运行时检查某个类实例的具体类型,或者把它当父类来解读。这套机制为多态打底,第 63 章会专门讲。
第三是反初始化(deinit)。类实例在被销毁前,可以执行一段自定义的清理代码,比如关闭文件、释放资源。结构体没有这个机制,因为它是第 66 章的主角。
第四是引用计数(reference counting)。同一个类实例可以被多个常量和变量同时指向。Swift 用自动引用计数(ARC)管理这类内存,第 75 章再深入。结构体是值类型,赋值时会整份拷贝,不存在多个引用指向同一份数据的问题。
1-7 一个容易踩的坑:常量也能改属性
类的引用语义会带来一个反直觉的现象,初学者经常在这里栽跟头。看下面这段代码:
let tenEighty = VideoMode()
tenEighty.frameRate = 25.0
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
print(tenEighty.frameRate)
// 输出 30.0
tenEighty 是用 let 声明的常量,可我们居然改了它的 frameRate。原因就藏在”引用类型”这四个字里:tenEighty 这个常量本身存储的并不是那个 VideoMode 对象,而是指向对象的引用(类似一个地址)。let 锁死的是”引用不能变”,也就是它永远指向同一个对象;至于对象里面的属性,只要属性本身是 var,就能照改不误。
更关键的是,alsoTenEighty = tenEighty 并没有复制出一份新对象,而是让两个常量指向了同一个对象。所以改了 alsoTenEighty.frameRate,tenEighty.frameRate 也跟着变。这正是引用类型和值类型最本质的分水岭。第 58 章我们会把这套差异彻底掰开讲。
1-8 什么时候该用类
说了这么多,给你一个实操判断:如果你需要把东西建模成”一份数据被多方共享、任何一方改动大家都看得到”,比如游戏里的玩家、网络请求管理器、需要继承复用逻辑的业务对象,那就用类。如果只是描述一个独立的值或局部状态,比如坐标、尺寸、配置项,结构体通常更合适。
别一上来就滥用类。Swift 的设计哲学是:能用值类型(struct、enum)解决的,就别用引用类型(class)。当你真正需要继承、需要 deinit 清理、需要多个地方共享同一份可变状态时,类才是那个对的工具。理解了这一点,你后面学继承、初始化、内存管理都会顺很多。