Swift4.2 下标

类、结构体和枚举可以定义 下标 ,它是用于访问集合,列表或序列的成员元素的快捷方式。 你可以直接使用下标来对值进行读写,而无需设置单独的读写方法。


例如,你可以将 Array 实例中的元素作为 someArray [index] 访问,将 Dictionary 实例中的元素作为 someDictionary [key] 访问。

你可以为单个类型定义多个下标,并根据传递给下标的索引值的类型,选择要使用的相应下标进行重载。 下标不限于单个维度,你可以定义具有多个输入参数的下标以满足你自定义类型的需求。

下标语法

使用下标, 让你可以通过在实例名称后面的方括号中写入一个或多个值来查询类的实例。它们的语法类似于实例方法和计算属性语法。使用 subscript 关键字定义下标,并且和实例方法类似,可以指定一个或多个输入参数和返回类型。与实例方法不同,下标可以是读写或只读。和计算属性类似, 读写是由 gettersetter 方法实现的:

1
2
3
4
5
6
7
8
subscript(index: Int) -> Int {
get {
// 在这里返回一个对应下标的值
}
set(newValue) {
// 在这里执行对应的赋值操作
}
}

newValue 的类型与下标的返回值相同。与计算属性一样,你可以选择不指定 setter 的 (newValue) 参数。但是如果你没有指定参数,那么 newValue 会成为你 setter 的默认参数。

与只读计算属性一样,你可以通过删除 get 关键字以及大括号来简化只读下标的声明:

1
2
3
subscript(index: Int) -> Int {
// 在这里返回一个对应下标的值
}

这是一个只读下标实现的例子,它定义了一个 TimesTable 结构体来表示一个 n 倍整数表:

1
2
3
4
5
6
7
8
9
struct TimesTable {
let multiplier: Int
subscript(index: Int) -> Int {
return multiplier * index
}
}
let threeTimesTable = TimesTable(multiplier: 3)
print("six times three is \(threeTimesTable[6])")
// 输出 "six times three is 18"

在这个例子中,创建了一个新的 TimesTable 实例来表示三倍表。通过将值 3 作为实例的 multiplier 参数值来初始化结构体。

你能够通过下标来调用 threeTimesTable 实例,比如调用 threeTimesTable[6]。这请求了三倍表中的第六项,返回 18 ,也就是 63 倍。

注意
n 倍表是基于固定的数学规则定义的。给 threeTimesTable[someIndex] 赋新值是不合适的,因此 TimesTable 的下标倍定义为只读下标。

下标的用法

下标的准确含义依赖于使用它的上下文。下标通常被用来作为访问集合、列表或序列中的元素的快捷方式。你可以用最合适的方式来为你的特定类或结构体函数实现下标。

例如,Swift 中的 Dictionary 类型实现了一个下标来设置和检索存储在 Dictionary 实例中的值。你可以通过在下标括号中提供一个符合字典键类型的键来设置字典的值,并且把符合字典值类型的值赋给下标:

1
2
var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
numberOfLegs["bird"] = 2

上面的例子定义了一个名为 numberOfLegs 的变量并使用含有三个键值对的字典初始化它。字典 numberOfLegs 的类型被推断为 [String: Int]。创建字典后,通过下标将一个 String 类型的键 「bird」 和一个 Int 类型的值 2 添加到了字典。

注意
Swift 的 Dictionary 类型将其键值作为下标实现并返回一个可选类型。对于上面的 numberOfLegs 字典,键值下标存取的值类型为 Int?,表示「可选的整数」Dictionary 类型用可选的下标类型来模拟不是每个键都有值的事实,可以通过为该键指定一个 nil 值来删除该键值。

下标选项

下标可以设置任意数量的输入参数,这些输入参数也可以是任意类型。 同时,下标也可以返回任何类型。 下标可以使用可变参数,但它们不能使用输入输出参数或是提供默认参数值。

类或结构体可以提供尽可能多的下标实现,并且能够基于当前下标括号内的值或者值的类型来推断合适的下标。定义多个下标被称为下标重载

虽然下标采用单参数是最常见的,但在合适的情况下也可以定义带多个参数的下标。下面的例子定义了一个 Matrix 结构体,这个结构体表示一个类型为 Double 的二维矩阵。Matrix 结构体的下标有两个整型参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct Matrix {
let rows: Int, columns: Int
var grid: [Double]
init(rows: Int, columns: Int) {
self.rows = rows
self.columns = columns
grid = Array(repeating: 0.0, count: rows * columns)
}
func indexIsValid(row: Int, column: Int) -> Bool {
return row >= 0 && row < rows && column >= 0 && column < columns
}
subscript(row: Int, column: Int) -> Double {
get {
assert(indexIsValid(row: row, column: column), "Index out of range")
return grid[(row * columns) + column]
}
set {
assert(indexIsValid(row: row, column: column), "Index out of range")
grid[(row * columns) + column] = newValue
}
}
}

Matrix 提供了一个带有两个名为 rowscolumns 参数的构造器,并构造一个足够大的数组来存储 Double 类型的 rows * columns 值。矩阵中的每个值初始化为 0.0 。为实现此目的,把数组的大小和初始单元格值 0.0 传递给数组构造器,该构造器会构造并初始化一个正确大小的新数组。

你可以通过传递合适的行数和列数给构造器来构造新的 Matrix 实例:

1
var matrix = Matrix(rows: 2, columns: 2)

上面的示例创建了一个新的带有两行两列的 Matrix 实例。Matrix 实例的 grid 数组实际上是矩阵从左上角到右下角的展开:

可以通过传递行和列的值到下标来给矩阵赋值,行和列用逗号隔开:

1
2
matrix[0, 1] = 1.5
matrix[1, 0] = 3.2

这两行代码调用下标的 setter 方法给矩阵的右上角(row0column1)赋值 1.5 和左下角(row1column0)赋值 3.2

Matrix 下标的 getter 方法和 setter 方法都包含一个断言来检查下标的 rowcolumn 是否有效。为了辅助这些断言,Matrix 包含一个名为 indexIsValid(row:column:) 的便捷方法,该方法检查请求的 rowcolumn 是否超出矩阵的边界:

1
2
3
func indexIsValid(row: Int, column: Int) -> Bool {
return row >= 0 && row < rows && column >= 0 && column < columns
}

如果尝试访问矩阵边距之外的下标就会触发断言:

1
2
let someValue = matrix[2, 2]
// 这里触发了断言,因为下标 [2, 2] 超出了矩阵的边界