14.1 构造器

结构体和类的实例在构造过程中会调用一种特殊的方法init,称为构造器。构造器init没有返回值,可以重载。在多个构造器重载的情况下,运行环境可以根据它的外部参数名或参数列表调用合适的构造器。

类似的方法在Objective-C中也称为构造器,在C++中称为构造函数。不同的是,Objective-C中的构造器有返回值,而C++中的构造函数名必须跟类名相同,没有返回值。

14.1.1 默认构造器

结构体和类在构造过程中会调用一个构造器,即便是没有编写任何构造器,它也是在里面的。下面看示例代码:

  1. class Rectangle {
  2. var width : Double = 0.0
  3. var height : Double = 0.0
  4. }
  5. var rect = Rectangle()
  6. rect.width = 320.0
  7. rect.height = 480.0
  8. println("长方形:\(rect.width) x \(rect.height)")

我们在上述代码第①行定义了Rectangle类,存储属性直接进行了初始化,在类中没有任何init的定义。第④行代码是创建实例的过程,这种代码在前面的学习过程中应该很常见了,那么我们有没有问过自己,为什么在类型后面要加一对小括号呢?小括号代表着方法的调用,Rectangle()表示调用了某个方法,这个方法就是构造器init()

事实上,在Rectangle的定义过程中省略了构造器,相当于如下代码:

  1. class Rectangle {
  2. var width : Double = 0.0
  3. var height : Double = 0.0
  4. init() {
  5. }
  6. }

如果Rectangle是结构体,则它的定义如下:

  1. struct Rectangle {
  2. var width : Double = 0.0
  3. var height : Double = 0.0
  4. }

而结构体Rectangle的默认构造器与类Rectangle的默认构造器是不同的,相当于如下代码:

  1. struct Rectangle {
  2. var width : Double = 0.0
  3. var height : Double = 0.0
  4. init() {
  5. }
  6. init(width : Double, height : Double) {
  7. self.width = width
  8. self.height = height
  9. }
  10. }

结构体Rectangle省略了一些构造器,除了第①行的无参数名的构造器init()之外,还有第②行的有参数名的构造器,该构造器是与存储属性相对应的,关于这种构造器我们还会在14.1.3节详细介绍。

从以上示例可以看出,类和结构体的默认构造也是有所不同的。要调用哪个构造器是根据传递的参数类型和参数名决定的。

14.1.2 构造器与存储属性初始化

构造器的主要作用就是初始化存储属性,我们在init()构造器中初始化存储属性widthheight后,那么在定义它们时就不需要初始化了。

修改Rectangle代码如下:

  1. calss Rectangle {
  2. var width : Double
  3. var height : Double
  4. init() {
  5. width = 0.0
  6. height = 0.0
  7. }
  8. }

如果存储属性在构造器中没有初始化,在定义的时候也没有初始化,那么就会发生编译错误。

构造器还可以初始化常量存储属性,下面我们看示例代码:

  1. class Employee {
  2. let no : Int
  3. var name : String?
  4. var job : String?
  5. var salary : Double
  6. var dept : Department?
  7. init() {
  8. no = 0
  9. salary = 0.0
  10. }
  11. }
  12. struct Department {
  13. let no : Int
  14. var name : String
  15. init() {
  16. no = 10
  17. name = "SALES"
  18. }
  19. }
  20. let dept = Department()
  21. var emp = Employee()

上述代码第①行和第⑩行分别定义了Employee类和Department结构体。其中,Employeeno属性(见第②行)和Departmentno属性(见第②行)都是常量类型属性。在我们学习常量的时候,曾讲过常量只能在定义的同时赋值,而在构造器中,常量属性可以不遵守这个规则,它们可以在构造器中赋值,参见代码第⑦行和第⑭行,这种赋值不能放在普通方法中。

另外,存储属性一般在定义的时候初始化。如果不能确定初始值,可以采用可选类型属性,见第③行、第④行和第⑥行代码。或者也可以在构造器中初始化,见代码第⑮行。

14.1.3 使用外部参数名

为了增强程序的可读性,Swift中的方法和函数可以使用外部参数名。在构造器中也可以使用外部参数名。构造器中的外部参数名要比一般的方法和函数更有意义,由于构造器命名都是init,如果一个对象类型中有多个构造器,我们就可以通过不同的外部参数名区分不同的构造器。

下面看示例代码:

  1. class RectangleA {
  2. var width : Double
  3. var height : Double
  4. init(W width : Double,H height : Double) {
  5. self.width = width
  6. self.height = height
  7. }
  8. }
  9. var recta = RectangleA(W : 320, H : 480)
  10. println("长方形A:\(recta.width) x \(recta.height)")

上述代码第①行是定义构造器init(W width : Double,H height : Double),这个构造器有两个参数widthheight,并且我们为参数提供了外部参数名WH

代码第②行和第③行是参数赋值给属性,其中使用了self关键字,表示当前实例,self.width表示当前实例的width属性,在参数命名与属性命名发生冲突时使用self,参数的作用域是构造器体,在参数与属性发生命名冲突时,参数屏蔽了属性,这种情况下引用属性前面要加self

第④行代码是创建RectangleA实例,这里使用了外部参数名。

提示 上述示例中,虽然我们定义的是类,但也完全适用于结构体。

外部参数名可以简化,在函数中可以在参数前加#,使得局部参数名变成外部参数名。但在构造器中就不用那么麻烦,构造器中的局部参数名可以直接作为外部参数名使用。

下面看示例代码:

  1. class RectangleB {
  2. var width : Double
  3. var height : Double
  4. init(width : Double, height : Double) {
  5. self.width = width
  6. self.height = height
  7. }
  8. }
  9. var rectb = RectangleB(width : 320, height : 480)
  10. println("长方形B:\(rectb.width) x \(rectb.height)")

上述代码第①行定义构造器init(width : Double, height : Double),其中没有声明外部参数名。在第②行代码调用构造器时,我们使用了外部参数名widthheight,这些外部参数名就是局部参数名。

前面介绍的几个示例适用于类和结构体,但以下写法只适用于结构体类型,如果在结构体中使用默认的构造器,则示例代码如下:

  1. struct RectangleD {
  2. var width : Double = 0.0
  3. var height : Double = 0.0
  4. }

代码中使用了默认的构造器,调用它的时候可以声明外部参数名,结构体类型可以按照从上到下的顺序,把属性名作为外部参数名,依次提供参数。构造器调用代码如下:

  1. var rectc = RectangleD(width : 320, height: 480)

widthheight是属性名,参数顺序是属性的定义顺序。

提示 这种写法是一种默认构造器,但只适用于结构体,在类中不能使用。