继承
本教程共 93 篇 · 第 61 篇 · 更新于 2026-08-08 · 约 8 分钟阅读
本节目标:学完你能用冒号让一个类继承另一个类,复用其成员,并用 super 调用父类版本的实现。
继承(inheritance)是类区别于结构体、枚举的根本能力。它允许一个类把另一个类的属性、方法、下标接过来用,还能在此基础上加新东西或改旧行为。通过继承,你能把通用逻辑放在父类,把特化逻辑放在子类,避免重复代码。这一章先把继承的骨架搭起来,重写细节留到第 62 章。
1-1 什么是基类
不被任何其他类继承的类,叫基类(base class)。Swift 有个特别之处:类不需要从一个统一的”总父类”继承。你直接写 class Foo {},它就是一个干净的基类,没有隐藏的祖宗。
class Vehicle {
var currentSpeed = 0.0
var description: String {
return "以 \(currentSpeed) 英里每小时行驶"
}
func makeNoise() {
// 基类啥也不做,留给孩子去实现
}
}
Vehicle 是个基类,它描述了”车”的共性:currentSpeed 速度、description 描述、以及一个空的 makeNoise() 发声方法。基类自己用处有限,但它是所有具体车型的地基。
1-2 用冒号写子类
让一个类继承另一个类,写法是在子类名后面加冒号,再写父类名:
class Bicycle: Vehicle {
var hasBasket = false
}
Bicycle 继承了 Vehicle,自动获得了 currentSpeed、description、makeNoise() 这些成员,自己只需加一个 hasBasket 属性。创建实例后,继承来的东西直接用:
let bicycle = Bicycle()
bicycle.currentSpeed = 15.0
print(bicycle.description) // 以 15.0 英里每小时行驶
子类还能再被继承,形成链条。比如双人自行车 Tandem 继承 Bicycle,而 Bicycle 继承 Vehicle,于是 Tandem 一路继承了全部三代的成员。继承链越长,最底层的类能用的能力越丰富。
1-3 重写是怎么发生的
子类可以提供自己的版本,去替换从父类继承来的方法、属性或下标,这叫重写(override)。比如 Train 继承 Vehicle 后,给 makeNoise() 一个具体实现:
class Train: Vehicle {
override func makeNoise() {
print("呜——")
}
}
这里 override 关键字是强制的。它有两个作用:一是明说”我就是要重写,不是不小心写了个同名方法”;二是让编译器去父类核对,确认父类真有这个可被重写的成员,防止你拼错名字或类型对不上。漏写 override 直接编译报错,这层保护能拦掉很多低级 bug。重写的完整规则我们在第 62 章展开。
1-4 用 super 调用父类版本
重写时,有时你不想完全丢掉父类逻辑,而是想”在父类基础上再加点料”。这时用 super 前缀去调父类的版本:
- 重写了方法,在方法体里写
super.someMethod()调用父类同名方法; - 重写了属性,在 getter/setter 里写
super.someProperty拿到父类的值; - 重写了下标,写
super[someIndex]访问父类版本。
class Car: Vehicle {
var gear = 1
override var description: String {
return super.description + ",挡位 \(gear)"
}
}
Car 重写了 description,但它没有把父类的描述推倒重来,而是先 super.description 拿到”以 x 英里每小时行驶”,再拼上自己的挡位信息。这样父类的心血被复用,子类只补差异。这种”调用父类再加工”的模式在继承里极常见。
1-5 阻止继承和重写:final
不是所有东西都该被改。如果你希望某个方法、属性、下标,或者整个类,不能被子类动,就用 final 修饰。
class LockedBase {
final var fixedID: Int = 0
final func lockedMethod() {}
}
final class Unbreakable {
// 整个类都被封死,不能当父类
}
final 写在关键字前面,比如 final var、final func、final class func、final subscript。任何子类试图重写被 final 的成员,都是编译错误。final class 则连被继承本身都不允许。注意 final 只能用在类成员上,顶层声明不能加 final。
什么时候用 final?当你确定某个行为就是最终版本、子类不该改它,或者这个类设计上就不该被扩展(比如为了性能或安全),就果断 final。它既是一种设计意图的声明,也是一道编译期的护栏。
1-6 继承不是白拿的
继承带来复用,也带来复杂度。每多一层继承,子类就多了一份对父类的依赖:父类一改,所有子类都可能受影响。所以 Swift 官方一直强调”优先用结构体”,继承只在确实需要”is-a”关系、且要复用或特化行为时才上。
另外记住:只有类能继承。结构体和枚举再想要”复用另一套类型的逻辑”,也只能走组合(把一个类型作为属性装进来)或协议(第 67 章),走不了继承。理解这条边界,你就不会在错误的类型上硬套继承。
1-7 小结
继承让你用 子类名: 父类名 复用并扩展父类成员;override 是重写的强制标记,漏写或写错都会编译失败;super 让你在重写时复用父类原版;final 则把成员或整个类锁死。把这套骨架记牢,第 62 章我们钻进重写的各种具体姿势。
1-8 继承链上的初始化约定
子类继承父类后,初始化责任也跟着传下来。子类必须保证:自己引入的属性有初值,并且调用父类初始化器让父类部分也就绪。这正是第 64 章二段式初始化的由来。继承越多层,这条”自底向上填属性、再自顶向下加工”的链路越长。新手在继承里最常踩的坑,就是忘了调 super.init(),或者调的时机不对,这些都会被编译器的安全检查拦下。
1-9 组合优先于继承
面向对象刚流行时,很多人什么都用继承,结果继承树又深又脆。Swift 社区更推崇”组合优于继承”:与其让 Car 继承 Vehicle 再加一堆东西,不如把”引擎""轮子”作为属性组合进 Car。组合更灵活、耦合更低,也不会被单一父类锁死。继承只在该用”is-a”关系、且要复用或特化行为时才合适。作为初学者,先想”这个类是不是另一种父类”,想清楚再决定继承,多数时候组合是更好的答案。苹果的很多 API 也在往组合与协议方向走,而不是深继承。把继承当作少数派工具,你的设计会更健壮。另外,过深的继承会让子类过度依赖父类实现细节,父类一改,子类可能莫名其妙出错。保持继承链浅、关系清晰,是写可维护 Swift 的基本功。
1-10 继承不是越多越好
工程经验告诉我们,继承层次越深,理解和维护成本越高。每多一层,子类就对父类多一份隐式依赖,父类一改,底下全受影响。所以 Swift 官方甚至不提供”总父类”,就是不想让你无脑往上挂。真正合适的继承通常只有一两层:基类放共性,子类放特化。如果发现自己想建三层以上的继承,先停下来想想能不能用组合或协议替代。浅而清晰的继承,远比深而精巧的继承好维护。