重载和重写。这两个概念的名称相似到让人不禁疑惑,为什么要这样命名。
它们是考试和面试中的常见题目,而容易混淆的主要原因就是名称。两者的工作原理完全不同:一个是创建多个同名函数,另一个是重新定义继承来的函数。
它们的确定时机也不同:一个在编译时,另一个在运行时。这一区别还会延伸到静态分派和动态分派的性能讨论。
本文将通过 Swift 示例,清楚地区分这两个概念。
下面是核心总结。
- 重载:定义多个同名但参数不同的函数。与继承无关
- 重写:子类重新定义父类的方法。以继承为前提
- 重载在编译时决定调用哪个函数;重写在运行时决定
- 重写是多态的核心机制
重载:同一个名称定义多个函数
重载是指定义多个名称相同、但参数配置(数量、类型、标签)不同的函数。
func area(radius: Double) -> Double { .pi * radius * radius }
func area(width: Double, height: Double) -> Double { width * height }
func area(side: Double) -> Double { side * side }
编译器会根据调用处传入的参数判断使用哪个函数。写下 area(radius: 3) 的瞬间,就确定使用第一个函数了。在执行前就已经决定。
想想没有重载的 C 语言,就能体会它的便利。绝对值函数会根据类型使用不同名称:abs、labs 和 fabs。重载让相同概念可以使用相同名称。
Swift 在标准库的各处都使用重载,例如 print(:) 和 min(:_:)。通过运算符重载,一个 + 还可以处理整数加法、字符串拼接和数组合并。
有一点需要注意:最好避免只有返回类型不同的重载。Swift 在某些情况下允许这种语法,但如果调用处没有明确指定类型,就会出现歧义错误,也会让读者困惑。
重写:重新定义继承来的方法
重写是指子类使用相同的签名,重新定义从父类继承的方法。继承关系是必要前提。
class Animal {
func speak() { print("...") }
}
class Dog: Animal {
override func speak() { print("汪汪") }
}
class Cat: Animal {
override func speak() { print("喵") }
}
关键就在这一幕。
let animals: [Animal] = [Dog(), Cat()]
for animal in animals {
animal.speak() // 汪汪,喵
}
变量类型是 Animal,但实际执行的是实际实例的方法。调用哪个 speak,会在运行时查看真实对象后决定。这就是动态分派,也是多态运行的机制。
Swift 强制要求使用 override 关键字,这一点也值得注意。编译器会捕获误创建与父类方法签名相同的方法,或因拼写错误导致重写失败的情况。反过来,在父类中加上 final 就可以禁止重写。
用表格一次比较
| 分类 | 重载 | 重写 |
|---|---|---|
| 定义 | 同名,不同参数 | 重新定义相同签名 |
| 继承 | 不需要 | 必须 |
| 确定时机 | 编译时(静态) | 运行时(动态) |
| 目的 | 名称一致性、API 便利性 | 多态、替换行为 |
| Swift 关键字 | 无 | override(必须),可用 final 禁止 |
确定时机的差异也与性能有关。编译时确定的调用(静态分派)可以直接跳转,而运行时决定的调用(动态分派)必须经过实际类型的方法表。在 Swift 中,加上 final 或 private 后,编译器就有机会将动态分派转换为静态分派并进行优化。
不再混淆的记忆方法
记住英文单词的字面含义即可。
Overload = 超载。就是在同一个名称上“装载”多个函数。一个名称,多种功能。
Override = 忽略并踩过去。就是“覆盖”父类的定义。定义只有一个,但替换其内容。
面试时可以这样开始回答。
“重载是放置多个同名但参数不同的函数,在编译时决定;重写是重新定义继承来的方法,在运行时根据实际类型决定。创建多态的是重写。”
总结
- 重载:定义多个同名、参数配置不同的函数。不需要继承
- 重写:子类以相同签名重新定义父类方法。必须继承
- 重载在编译时(静态)决定调用目标,重写则在运行时(动态)决定
- 多态的核心机制是重写和动态分派
- Swift 通过强制使用 override 关键字来防止错误,也可以用 final 禁止重写
- 记忆方法:Overload 给名称加上多个功能,Override 覆盖原有定义

