14.1 构造器
结构体和类的实例在构造过程中会调用一种特殊的方法init
,称为构造器。构造器init
没有返回值,可以重载。在多个构造器重载的情况下,运行环境可以根据它的外部参数名或参数列表调用合适的构造器。
类似的方法在Objective-C中也称为构造器,在C++中称为构造函数。不同的是,Objective-C中的构造器有返回值,而C++中的构造函数名必须跟类名相同,没有返回值。
14.1.1 默认构造器
结构体和类在构造过程中会调用一个构造器,即便是没有编写任何构造器,它也是在里面的。下面看示例代码:
class Rectangle { ①
var width : Double = 0.0 ②
var height : Double = 0.0 ③
}
var rect = Rectangle() ④
rect.width = 320.0 ⑤
rect.height = 480.0 ⑥
println("长方形:\(rect.width) x \(rect.height)")
我们在上述代码第①行定义了Rectangle
类,存储属性直接进行了初始化,在类中没有任何init
的定义。第④行代码是创建实例的过程,这种代码在前面的学习过程中应该很常见了,那么我们有没有问过自己,为什么在类型后面要加一对小括号呢?小括号代表着方法的调用,Rectangle()
表示调用了某个方法,这个方法就是构造器init()
。
事实上,在Rectangle
的定义过程中省略了构造器,相当于如下代码:
class Rectangle {
var width : Double = 0.0
var height : Double = 0.0
init() { ①
}
}
如果Rectangle
是结构体,则它的定义如下:
struct Rectangle {
var width : Double = 0.0
var height : Double = 0.0
}
而结构体Rectangle
的默认构造器与类Rectangle
的默认构造器是不同的,相当于如下代码:
struct Rectangle {
var width : Double = 0.0
var height : Double = 0.0
init() { ①
}
init(width : Double, height : Double) { ②
self.width = width
self.height = height
}
}
结构体Rectangle
省略了一些构造器,除了第①行的无参数名的构造器init()
之外,还有第②行的有参数名的构造器,该构造器是与存储属性相对应的,关于这种构造器我们还会在14.1.3节详细介绍。
从以上示例可以看出,类和结构体的默认构造也是有所不同的。要调用哪个构造器是根据传递的参数类型和参数名决定的。
14.1.2 构造器与存储属性初始化
构造器的主要作用就是初始化存储属性,我们在init()
构造器中初始化存储属性width
和height
后,那么在定义它们时就不需要初始化了。
修改Rectangle
代码如下:
calss Rectangle {
var width : Double
var height : Double
init() {
width = 0.0
height = 0.0
}
}
如果存储属性在构造器中没有初始化,在定义的时候也没有初始化,那么就会发生编译错误。
构造器还可以初始化常量存储属性,下面我们看示例代码:
class Employee { ①
let no : Int ②
var name : String? ③
var job : String? ④
var salary : Double ⑤
var dept : Department? ⑥
init() { ⑦
no = 0 ⑧
salary = 0.0 ⑨
}
}
struct Department { ⑩
let no : Int ⑪
var name : String ⑫
init() { ⑬
no = 10 ⑭
name = "SALES" ⑮
}
}
let dept = Department()
var emp = Employee()
上述代码第①行和第⑩行分别定义了Employee
类和Department
结构体。其中,Employee
的no
属性(见第②行)和Department
的no
属性(见第②行)都是常量类型属性。在我们学习常量的时候,曾讲过常量只能在定义的同时赋值,而在构造器中,常量属性可以不遵守这个规则,它们可以在构造器中赋值,参见代码第⑦行和第⑭行,这种赋值不能放在普通方法中。
另外,存储属性一般在定义的时候初始化。如果不能确定初始值,可以采用可选类型属性,见第③行、第④行和第⑥行代码。或者也可以在构造器中初始化,见代码第⑮行。
14.1.3 使用外部参数名
为了增强程序的可读性,Swift中的方法和函数可以使用外部参数名。在构造器中也可以使用外部参数名。构造器中的外部参数名要比一般的方法和函数更有意义,由于构造器命名都是init
,如果一个对象类型中有多个构造器,我们就可以通过不同的外部参数名区分不同的构造器。
下面看示例代码:
class RectangleA {
var width : Double
var height : Double
init(W width : Double,H height : Double) { ①
self.width = width ②
self.height = height ③
}
}
var recta = RectangleA(W : 320, H : 480) ④
println("长方形A:\(recta.width) x \(recta.height)")
上述代码第①行是定义构造器init(W width : Double,H height : Double)
,这个构造器有两个参数width
和height
,并且我们为参数提供了外部参数名W
和H
。
代码第②行和第③行是参数赋值给属性,其中使用了self
关键字,表示当前实例,self.width
表示当前实例的width
属性,在参数命名与属性命名发生冲突时使用self
,参数的作用域是构造器体,在参数与属性发生命名冲突时,参数屏蔽了属性,这种情况下引用属性前面要加self
。
第④行代码是创建RectangleA
实例,这里使用了外部参数名。
提示 上述示例中,虽然我们定义的是类,但也完全适用于结构体。
外部参数名可以简化,在函数中可以在参数前加#
,使得局部参数名变成外部参数名。但在构造器中就不用那么麻烦,构造器中的局部参数名可以直接作为外部参数名使用。
下面看示例代码:
class RectangleB {
var width : Double
var height : Double
init(width : Double, height : Double) { ①
self.width = width
self.height = height
}
}
var rectb = RectangleB(width : 320, height : 480) ②
println("长方形B:\(rectb.width) x \(rectb.height)")
上述代码第①行定义构造器init(width : Double, height : Double)
,其中没有声明外部参数名。在第②行代码调用构造器时,我们使用了外部参数名width
和height
,这些外部参数名就是局部参数名。
前面介绍的几个示例适用于类和结构体,但以下写法只适用于结构体类型,如果在结构体中使用默认的构造器,则示例代码如下:
struct RectangleD {
var width : Double = 0.0
var height : Double = 0.0
}
代码中使用了默认的构造器,调用它的时候可以声明外部参数名,结构体类型可以按照从上到下的顺序,把属性名作为外部参数名,依次提供参数。构造器调用代码如下:
var rectc = RectangleD(width : 320, height: 480)
width
和height
是属性名,参数顺序是属性的定义顺序。
提示 这种写法是一种默认构造器,但只适用于结构体,在类中不能使用。