构造过程是使用类、结构体或枚举类型的实例之前的准备过程。在新实例使用前有个过程是必须的,它包括设置实例中每个存储
属性的初始值和执⾏其他必须的设置或构造过程。
你要通过定义构造器来实现构造过程,它就像用来创建特定类型新实例的特殊方法。与 Objective-C 中的构造器不同, Swift 的
构造器没有返回值。它们的主要任务是保证某种类型的新实例在第一次使用前完成正确的初始化。
类的实例也可以通过实现析构器来执⾏它释放之前自定义的清理工作。想了解更多关于析构器的内容,请参考《析构过程》。
存储属性的初始赋值
类和结构体在创建实例时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。你可以在构造
器中为存储型属性设置初始值,也可以在定义属性时分配默认值。以下小节将详细介绍这两种方法。
注意
当你为存储型属性分配默认值或者在构造器中设置初始值时,它们的值是被直接设置的,不会触发任何属性观察者。
构造器
构造器在创建某个特定类型的新实例时被调用。它的最简形式类似于一个不带任何形参的实例方法,以关键字 init 命名:
init() {
// 在此处执⾏构造过程
}
下⾯例子中定义了一个⽤来保存华氏温度的结构体 Fahrenheit ,它拥有一个 Double 类型的存储型属性temperature :
struct Fahrenheit {
var temperature: Double
init() {
temperature = 32.0
}
}
var f = Fahrenheit()
print("The default temperature is \(f.temperature)° Fahrenheit")
// 打印“The default temperature is 32.0° Fahrenheit”
这个结构体定义了一个不带形参的构造器 init ,并在⾥面将存储型属性 temperature 的值初始化为 32.0 (华⽒温度下水的冰点)。
默认属性值
如前所述,你可以在构造器中为存储型属性设置初始值。同样,你也可以在属性声明时为其设置默认值。
注意
如果一个属性总是使用相同的初始值,那么为其设置一个默认值比每次都在构造器中赋值要好。两种方法的最终结果是一样的,
只不过使用默认值让属性的初始化和声明结合得更紧密。它能让你的构造器更简洁、更清晰,且能通过默认值自动推导出属性的
类型;同时,它也能让你充分利用默认构造器、构造器继承等特性,后续章节将讲到。
你可以通过在属性声明时为 temperature 提供默认值来使⽤更简单的方式定义结构体 Fahrenheit :
struct Fahrenheit {
var temperature = 32.0
}
⾃定义构造过程
你可以通过输入形参和可选属性类型来自定义构造过程,也可以在构造过程中分配常量属性。这些都将在后面章节中提到。
形参的构造过程
自定义构造过程时,可以在定义中提供构造形参,指定其值的类型和名字。构造形参的功能和语法跟函数和方法的形参相同。
下⾯例子中定义了一个用来保存摄氏温度的结构体 Celsius 。它定义了两个不同的构造器: init(fromFahrenheit:) 和
init(fromKelvin:) ,二者分别通过接受不同温标下的温度值来创建新的实例:
struct Celsius {
var temperatureInCelsius: Double
init(fromFahrenheit fahrenheit: Double) {
temperatureInCelsius = (fahrenheit - 32.0) / 1.8
}
init(fromKelvin kelvin: Double) {
temperatureInCelsius = kelvin - 273.15
}
}
let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius 是 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius 是 0.0
第一个构造器拥有一个构造形参,其实参标签为 fromFahrenheit ,形参命名为 fahrenheit ;第二个构造器也拥有一个构造形参,
其实参标签为 fromKelvin ,形参命名为 kelvin 。这两个构造器都将单一的实参转换成摄氏温度值, 并保存在属性
temperatureInCelsius 中。
形参命名和实参标签
跟函数和方法形参相同,构造形参可以同时使用在构造器⾥使用的形参命名和一个外部调用构造器时使用的实参标签。
然而,构造器并不像函数和方法那样在括号前有一个可辨别的方法名。因此在调用构造器时,主要通过构造器中形参命名和类型
来确定应该被调用的构造器。正因如此,如果你在定义构造器时没有提供实参标签,Swift 会为构造器的每个形参自动生成一个
实参标签。
以下例子中定义了一个结构体 Color ,它包含了三个常量: red 、 green 和 blue 。这些属性可以存储 0.0 到1.0 之间的值,⽤来表
明颜色中红、绿、蓝成分的含量。
Color 提供了一个构造器,为红蓝绿提供三个合适 Double 类型的形参命名。 Color 也提供了第二个构造器,它只包含名为 white
的 Double 类型的形参,它为三个颜色的属性提供相同的值。
struct Color {
let red, green, blue: Double
init(redColor red:Double,_ green:Double,blue:Double) { //red就是形参,redColor就是实参标签,这个和函数是一样的
self.red = red
self.green = green
self.blue = blue
}
init(white: Double) {
red = white
green = white
blue = white
}
}
两种构造器都能通过为每一个构造器形参提供命名值来创建一个新的 Color 实例:
let magenta = Color(redColor: 1.0, 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)
注意,如果不通过实参标签传值,这个构造器是没法调用的。如果构造器定义了某个实参标签,就必须使用它,忽略它将导致编
译期错误:
let veryGreen = Color(0.0, 1.0, 0.0)
// 报编译期错误-需要实参标签
不带实参标签的构造器形参
如果你不希望构造器的某个形参使用实参标签,可以使用下划线( _ )来代替显式的实参标签来重写默认⾏为。 下⾯是之前形参的
构造过程中 Celsius 例子的扩展,多了一个用已经存在的摄⽒表示的 Double 类型值来创建新的 Celsius 实例的额外构造器:
struct Celsius {
var temperatureInCelsius: Double
init(fromFahrenheit fahrenheit: Double) {
temperatureInCelsius = (fahrenheit - 32.0) / 1.8
}
init(fromKelvin kelvin: Double) {
temperatureInCelsius = kelvin - 273.15
}
init(_ celsius: Double){
temperatureInCelsius = celsius
}
}
let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius 为 37.0
构造器调用 Celsius(37.0) 意图明确,不需要实参标签。因此适合使用 init(_ celsius: Double) 这样的构造器,从⽽可以通过提供未
命名的 Double 值来调用构造器。
可选属性类型
如果你自定义的类型有一个逻辑上允许值为空的存储型属性——无论是因为它无法在初始化时赋值,还是因为它在之后某个时机
可以赋值为空——都需要将它声明为可选类型 。可选类型的属性将自动初始化为 nil ,表示这个属性是特意在构造过程设置为
空。
下⾯例子中定义了类 SurveyQuestion ,它包含一个可选 String 属性 response :
class SurveyQuestion {
var text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
print(text)
}
}
let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")
cheeseQuestion.ask()
// 打印“Do you like cheese?”
cheeseQuestion.response = "Yes, I do like cheese."
调查问题的答案在询问前是无法确定的,因此我们将属性 response 声明为 String? 类型,或者说是 “可选类型String “。当
SurveyQuestion 的实例初始化时,它将自动赋值为 nil ,表明“暂时还没有字符“。
构造过程中常量属性的赋值
你可以在构造过程中的任意时间点给常量属性赋值,只要在构造过程结束时把它设置成确定的值。一旦常量属性被赋值,它将永
远不可更改。
注意
对于类的实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。
你可以修改上面的 SurveyQuestion 示例,⽤常量属性替代变量属性 text ,表示问题内容 text 在SurveyQuestion 的实例被创建之
后不会再被修改。尽管 text 属性现在是常量,我们仍然可以在类的构造器中设置它的值:
class SurveyQuestion {
let text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
print(text)
}
}
let beetsQuestion = SurveyQuestion(text: "How about beets?")
beetsQuestion.ask()
// 打印“How about beets?”
beetsQuestion.response = "I also like beets. (But not with cheese.)"
默认构造器
如果结构体或类为所有属性提供了默认值,又没有提供任何自定义的构造器,那么 Swift 会给这些结构体或类提供一个默认构造
器。这个默认构造器将简单地创建一个所有属性值都设置为它们默认值的实例。
下⾯例子中定义了一个类 ShoppingListItem ,它封装了购物清单中的某一物品的名字( name )、数量 ( quantity )和购买状态
( purchase ) :
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
由于 ShoppingListItem 类中的所有属性都有默认值,且它是没有父类的基类,它将自动获得一个将为所有属性设置默认值的并创
建实例的默认构造器(由于 name 属性是可选 String 类型,它将接收一个默认 nil 的默认值,尽管代码中没有写出这个值)。上面例
子中使用默认构造器创造了一个 ShoppingListItem 类的实例(使用 ShoppingListItem() 形式的构造器语法),并将其赋值给变量
item 。
结构体的逐一成员构造器
结构体如果没有定义任何自定义构造器,它们将自动获得一个逐一成员构造器(memberwise initializer)。不像默认构造器,即
使存储型属性没有默认值,结构体也能获得逐一成员构造器。
逐一成员构造器是用来初始化结构体新实例⾥成员属性的快捷⽅法。新实例的属性初始值可以通过名字传入逐一成员构造器中。
下⾯例子中定义了一个结构体 Size ,它包含两个属性 width 和 height 。根据这两个属性默认赋值为 0.0 ,它 们的类型被推断出
来为 Double 。
结构体 Size 自动获得了一个逐一成员构造器 init(width:height:) 。你可以用它来创建新的 Size 实例:
struct Size {
var width = 0.0,
height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
当你调用一个逐一成员构造器(memberwise initializer)时,可以省略任何一个有默认值的属性。在上面这个例子中, Size 结构
体的 height 和 width 属性各有一个默认值。你可以省略两者或两者之一,对于被省略的属性,构造器会使用默认值。举个例⼦:
let zeroByTwo = Size(height: 2.0)
print(zeroByTwo.width, zeroByTwo.height)
// 打印 "0.0 2.0"
let zeroByZero = Size()
print(zeroByZero.width, zeroByZero.height)
// 打印 "0.0 0.0"
值类型的构造器代理
构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能避免多个构造器间的代码重复。
构造器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相
对简单,因为它们只能代理给自己的其它构造器。类则不同,它可以继承自其它类(请参考《继承》)。这意味着类有责任保证其
所有继承的存储型属性在构造时也能正确的初始化。这些责任将在后续章节《类的继承和构造过程》中介绍。
对于值类型,你可以使用 self.init 在自定义的构造器中引用相同类型中的其它构造器。并且你只能在构造器内部调用 self.init
请注意,如果你为某个值类型定义了一个自定义的构造器,你将无法访问到默认构造器(如果是结构体,还将无法访问逐一成员构
造器)。这种限制避免了在一个更复杂的构造器中做了额外的重要设置,但有人不小心使用自动生成的构造器而导致错误的情况。
注意
假如你希望默认构造器、逐一成员构造器以及你⾃己的自定义构造器都能用来创建实例,可以将自定义的构造器写到扩展
( extension )中,⽽不是写在值类型的原始定义中(理论上我们都应该这么写)。想查看更多内容,请查看《扩展》章节。
下⾯例子定义一个自定义结构体 Rect ,用来代表几何矩形。这个例子需要两个辅助的结构体 Size 和 Point ,它们各自为其所有
的属性提供了默认初始值 0.0 。
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
你可以通过以下三种方式为 Rect 创建实例——使用含有默认值的 origin 和 size 属性来初始化;提供指定的origin 和 size 实例来初
始化;提供指定的 center 和 size 来初始化。在下⾯ Rect 结构体定义中,我们为这三种方式提供了三个自定义的构造器:
struct Rect {
var origin = Point()
var size = Size()
init() {}
init(origin: Point, size: Size) {
self.origin = origin
self.size = size
}
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
第一个 Rect 构造器 init() ,在功能上跟没有自定义构造器时自动获得的默认构造器是一样的。这个构造器的函数体是空的,使⽤
用一对大括号 {} 来表示。调用这个构造器将返回一个 Rect 实例,它的 origin 和 size 属性都使用定义时的默认值 Point(x: 0.0, y:
0.0) 和 Size(width: 0.0, height: 0.0) :
let basicRect = Rect()
// basicRect 的 origin 是 (0.0, 0.0),size 是 (0.0, 0.0)
第二个 Rect 构造器 init(origin:size:) ,在功能上跟结构体在没有自定义构造器时获得的逐一成员构造器是一样的。这个构造器只
是简单地将 origin 和 size 的实参值赋给对应的存储型属性:
let originRect = Rect(origin: Point(x: 2.0, y: 2.0), size: Size(width: 5.0, height: 5.0))
// originRect 的 origin 是 (2.0, 2.0),size 是 (5.0, 5.0)
第三个 Rect 构造器 init(center:size:) 稍微复杂一点。它先通过 center 和 size 的值计算出 origin 的坐标,然后再调用(或者说代理
给) init(origin:size:) 构造器来将新的 origin 和 size 值赋值到对应的属性中:
let centerRect = Rect(center: Point(x: 4.0, y: 4.0), size: Size(width: 3.0, height: 3.0))
// centerRect 的 origin 是 (2.5, 2.5),size 是 (3.0, 3.0)
构造器 init(center:size:) 可以直接将 origin 和 size 的新值赋值到对应的属性中。然而,构造器init(center:size:) 通过使用提供了
了相关功能的现有构造器将会更加便捷(而且意图更清晰)。
注意
如果你想用另外一种不需要⾃己定义 init() 和 init(origin:size:) 的方式来实现这个例子,请参考《扩展》。
类的继承和构造过程
类⾥⾯的所有存储型属性——包括所有继承自父类的属性——都必须在构造过程中设置初始值。
Swift 为类型提供了两种构造器来确保实例中所有存储型属性都能获得初始值,它们被称为指定构造器和便利构造器。
指定构造器和便利构造器
指定构造器是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并调用合适的父类构造器让构造过程沿着⽗
父类链继续往上进⾏。
类倾向于拥有极少的指定构造器,普遍的是一个类只拥有一个指定构造器。指定构造器像一个个“漏斗”放在构造过程发⽣的地
⽅,让构造过程沿着父类链继续往上进⾏。
每一个类都必须⾄少拥有一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。具体内容
请参考后续章节《构造器的⾃动继承》。
便利构造器是类中⽐较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为部分形参提供默
认值。你也可以定义便利构造器来创建一个特殊用途或特定输入值的实例。
你应当只在必要的时候为类提供便利构造器,⽐方说某种情况下通过使用便利构造器来快捷调用某个指定构造器,能够节省更多
开发时间并让类的构造过程更清晰明了。
指定构造器和便利构造器的语法
类的指定构造器的写法跟值类型简单构造器一样:
init(parameters) {
statements
}
便利构造器也采用相同样式的写法,但需要在 init 关键字之前放置 convenience 关键字,并使用空格将它们俩分开:
convenience init(parameters) {
statements
}
类类型的构造器代理
为了简化指定构造器和便利构造器之间的调用关系,Swift 构造器之间的代理调用遵循以下三条规则:
规则 1
指定构造器必须调用其直接父类的的指定构造器。
规则 2
便利构造器必须调用同类中定义的其它构造器。
规则 3
便利构造器最后必须调用指定构造器。
一个更方便记忆的方法是:
1.指定构造器必须总是向上代理
2.便利构造器必须总是横向代理
这些规则可以通过下面图例来说明:
如图所示,父类中包含一个指定构造器和两个便利构造器。其中一个便利构造器调用了另外一个便利构造器,⽽后者又调用了唯
一的指定构造器。这满足了上面提到的规则 2 和 3。这个父类没有自己的父类,所以规则 1 没有用到。
⼦类中包含两个指定构造器和一个便利构造器。便利构造器必须调用两个指定构造器中的任意一个,因为它只能调用同一 个类⾥
的其他构造器。这满足了上面提到的规则 2 和 3。而两个指定构造器必须调用父类中唯一的指定构造器,这满足了规则 1。
注意
这些规则不会影响类的实例如何创建。任何上图中展示的构造器都可以用来创建完全初始化的实例。这些规则只影响类的构造器
如何实现。
下⾯图例中展示了一种涉及四个类的更复杂的类层级结构。它演示了指定构造器是如何在类层级中充当“漏斗”的作用,在类的构
造器链上简化了类之间的相互关系。
两段式构造过程
Swift 中类的构造过程包含两个阶段。第一个阶段,类中的每个存储型属性赋一个初始值。当每个存储型属性的初始值被赋值
后,第二阶段开始,它给每个类一次机会,在新实例准备使用之前进一步自定义它们的存储型属性。
两段式构造过程的使用让构造过程更安全,同时在整个类层级结构中给予了每个类完全的灵活性。两段式构造过程可以防止属性
值在初始化之前被访问,也可以防止属性被另外一个构造器意外地赋予不同的值。
注意
Swift 的两段式构造过程跟 Objective-C 中的构造过程类似。最主要的区别在于阶段 1,Objective-C 给每一个属性赋值 0或空值
(⽐如说 0 或 nil )。Swift 的构造流程则更加灵活,它允许你设置定制的初始值,并自如应对某些属性不能以 0 或 nil 作为合法默
认值的情况。(不要管它说什么一直往下看,下面有图)
Swift 编译器将执⾏ 4 种有效的安全检查,以确保两段式构造过程不出错地完成:
安全检查 1
指定构造器必须保证它所在类的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。 如上
所述,一个对象的内存只有在其所有存储型属性确定之后才能完全初始化。为了满⾜这一规则,指定构造器必须保证它所在类的
属性在它往上代理之前先完成初始化。
安全检查 2
指定构造器必须在为继承的属性设置新值之前向上代理调用父类构造器。如果没这么做,指定构造器赋予的新值将被父类中
的构造器所覆盖。
安全检查 3
便利构造器必须为任意属性(包括所有同类中定义的)赋新值之前代理调用其它构造器。如果没这么做,便利构造器赋予的新
值将被该类的指定构造器所覆盖。
安全检查 4
构造器在第一阶段构造完成之前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用 self 作为一个 值。
类的实例在第一阶段结束以前并不是完全有效的。只有第一阶段完成后,类的实例才是有效的,才能访问属性和调用⽅法。
以下是基于上述安全检查的两段式构造过程展示:
阶段 1
1.类的某个指定构造器或便利构造器被调用。
2.完成类的新实例内存的分配,但此时内存还没有被初始化。
3.指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化。
4.指定构造器切换到父类的构造器,对其存储属性完成相同的任务。
5.这个过程沿着类的继承链一直往上执⾏,直到到达继承链的最顶部。
6.当到达了继承链最顶部,而且继承链的最后一个类已确保所有的存储型属性都已经赋值,这个实例的内存被认为已经完全初始
化。此时阶段 1 完成。
阶段 2
1.从继承链顶部往下,继承链中每个类的指定构造器都有机会进一步自定义实例。构造器此时可以访问 self 、修改它的属性并调
⽤实例方法等等。
2.最终,继承链中任意的便利构造器有机会自定义实例和使用 self 。
下图展示了在假定的子类和父类之间的构造阶段 1:
在这个例子中,构造过程从对子类中一个便利构造器的调⽤开始。这个便利构造器此时还不能修改任何属性,它会代理到该类中
的指定构造器。
如安全检查 1 所示,指定构造器将确保所有子类的属性都有值。然后它将调用父类的指定构造器,并沿着继承链一直往上完成父
类的构造过程。
⽗类中的指定构造器确保所有父类的属性都有值。由于没有更多的父类需要初始化,也就无需继续向上代理。
一旦父类中所有属性都有了初始值,实例的内存被认为是完全初始化,阶段 1 完成。
以下展示了相同构造过程的阶段 2:
⽗类中的指定构造器现在有机会进一步自定义实例(尽管这不是必须的)。
一旦⽗类中的指定构造器完成调用,子类中的指定构造器可以执⾏更多的自定义操作(这也不是必须的)。
最终,一旦子类的指定构造器完成调用,最开始被调用的便利构造器可以执⾏更多的自定义操作。
构造器的继承和重写
跟 Objective-C 中的子类不同,Swift 中的子类默认情况下不会继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构
造器被一个更精细的子类继承,而在用来创建子类时的新实例时没有完全或错误被初始化。
注意
父类的构造器仅会在安全和适当的某些情况下被继承。具体内容请参考后续章节《构造器的⾃动继承》。
假如你希望自定义的子类中能提供一个或多个跟父类相同的构造器,你可以在子类中提供这些构造器的自定义实现。
当你在编写一个和父类中指定构造器相匹配的子类构造器时,你实际上是在重写父类的这个指定构造器。因此,你必须在定义子
类构造器时带上 override 修饰符。即使你重写的是系统自动提供的默认构造器,也需要带上 override 修饰符,具体内容请参考
《默认构造器》。
正如重写属性,方法或者是下标, override 修饰符会让编译器去检查父类中是否有相匹配的指定构造器,并验证构造器参数是否
被按预想中被指定。
注意
当你重写一个父类的指定构造器时,你总是需要写 override 修饰符,即使是为了实现子类的便利构造器。
相反,如果你编写了一个和父类便利构造器相匹配的子类构造器,由于子类不能直接调用父类的便利构造器(每个规则都在上文
《类的构造器代理规则》有所描述),因此,严格意义上来讲,你的子类并未对一个父类构造器提供重写。最后的结果就是,你在
⼦类中“重写”一个父类便利构造器时,不需要加 override 修饰符。
在下⾯的例子中定义了⼀个叫 Vehicle 的基类。基类中声明了一个存储型属性 numberOfWheels ,它是默认值为Int 类型的 0 。
numberOfWheels 属性用在一个描述车辆特征 String 类型为 descrpiption 的计算型属性中:
class Vehicle {
var numberOfWheels = 0
var description: String {
return "\(numberOfWheels) wheel(s)"
}
}
Vehicle 类只为存储型属性提供默认值,也没有提供自定义构造器。因此,它会自动获得一个默认构造器,具体内容请参考
《默认构造器》。默认构造器(如果有的话)总是类中的指定构造器,可以用于创建 numberOfWheels 为 0 的 Vehicle 实例:
let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)
下⾯例子中定义了一个 Vehicle 的子类 Bicycle :
class Bicycle: Vehicle {
override init() {
super.init()
numberOfWheels = 2 //如果没写这句话,上面的super.init() 可以省略
}
}
⼦类 Bicycle 定义了一个自定义指定构造器 init() 。这个指定构造器和父类的指定构造器相匹配,所以 Bicycle中这个版本的构造
器需要带上 override 修饰符。
Bicycle 的构造器 init() 以调用 super.init() 方法开始,这个⽅法的作用是调用 Bicycle 的父类Vehicle 的默认构造器。这样可以确
保 Bicycle 在修改属性之前,它所继承的属性 numberOfWheels 能被Vehicle 类初始化。在调用 super.init() 之后,属性
numberOfWheels 的原值被新值 2 替换。
如果你创建一个 Bicycle 实例,你可以调用继承的 description 计算型属性去查看属性 numberOfWheels 是否有改变:
let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)") // 打印“Bicycle: 2 wheel(s)”
如果子类的构造器没有在阶段 2 过程中做自定义操作,并且父类有一个无参数的自定义构造器。你可以在所有父类的存储属性赋
值之后省略 super.init() 的调用。
这个例子定义了另一个 Vehicle 的子类 Hoverboard ,只设置它的 color 属性。这个构造器依赖隐式调用父类的构造器来完成,而
不是显示调用 super.init() 。
class Hoverboard: Vehicle {
var color: String
init(color: String) {
self.color = color
// super.init() 在这⾥被隐式调用
}
override var description: String {
return "\(super.description) in a beautiful \(color)"
}
}
Hoverboard 的实例用 Vehicle 构造器⾥默认的轮子数量。
let hoverboard = Hoverboard(color: "silver")
print("Hoverboard: \(hoverboard.description)")
// Hoverboard: 0 wheel(s) in a beautiful silver
注意
子类可以在构造过程修改继承来的变量属性,但是不能修改继承来的常量属性。
构造器的⾃动继承
如上所述,⼦类在默认情况下不会继承父类的构造器。但是如果满足特定条件,⽗类构造器是可以被自动继承的。事实 上,这意
味着对于许多常见场景你不必重写父类的构造器,并且可以在安全的情况下以最小的代价继承父类的构造器。
假设你为⼦类中引入的所有新属性都提供了默认值,以下 2 个规则将适用:
规则 1
如果子类没有定义任何指定构造器,它将自动继承父类所有的指定构造器。
规则 2
如果⼦类提供了所有父类指定构造器的实现——⽆论是通过规则 1 继承过来的,还是提供了自定义实现——它将自动继承父类
所有的便利构造器。
即使你在子类中添加了更多的便利构造器,这两条规则仍然适用。
注意
⼦类可以将父类的指定构造器实现为便利构造器来满足规则 2。