Swift 下标
下标(subscript)是访问集合、序列或自定义类型中元素的快捷方式,用方括号语法 实例[索引] 取值和赋值。
数组的 array[0]、字典的 dict["key"] 都是下标,你可以在自己的类、结构体或枚举里定义同样的语法。
下标语法与应用
定义下标用 subscript 关键字,写法介于实例方法和计算属性之间:有参数列表和返回类型,但没有名字。
实例
struct TimesTable {
let multiplier: Int
// 只读下标可以省略 get 关键字和花括号
subscript(index: Int) -> Int {
return multiplier * index
}
}
let threeTimesTable = TimesTable(multiplier: 3)
print("3 的 6 倍是 \(threeTimesTable[6])")
运行结果:
3 的 6 倍是 18
需要同时支持读写时,写下标参数的完整形式,用 get 和 set 分别处理取值与赋值。
实例
struct Weekday {
private var days = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"]
subscript(index: Int) -> String {
get {
return days[index]
}
set {
days[index] = newValue
}
}
}
var week = Weekday()
print(week[1])
week[0] = "RUNOOB"
print(week[0])
运行结果:
Monday RUNOOB
set 中的新值默认叫 newValue,也可以写成 set(value) 自定义名字。
下标可以接收任意数量的参数,参数类型也不受限制,因此同一个类型可以提供多个下标实现,靠参数类型和个数来区分,这就是下标的重载。
实例
struct Matrix {
let rows: Int, columns: Int
var grid: [Double]
init(rows: Int, columns: Int) {
self.rows = rows
self.columns = columns
// 用一维数组模拟二维矩阵,所有元素初始为 0.0
grid = Array(repeating: 0.0, count: rows * columns)
}
// 下标可以接收多个参数
subscript(row: Int, column: Int) -> Double {
get {
return grid[(row * columns) + column]
}
set {
grid[(row * columns) + column] = newValue
}
}
}
var mat = Matrix(rows: 3, columns: 3)
mat[0, 0] = 1.0
mat[0, 1] = 2.0
mat[1, 0] = 3.0
mat[1, 1] = 5.0
print(mat[0, 0], mat[0, 1], mat[1, 0], mat[1, 1])
运行结果:
1.0 2.0 3.0 5.0
Matrix 用一个一维数组保存数据,下标负责把二维坐标 (row, column) 换算成一维偏移量 row * columns + column。
只读下标简写
只提供 get 的下标是只读下标,此时可以省略 get 关键字和外面那层花括号,直接写返回语句。
上面的 TimesTable 就是这种简写。相比之下,Weekday 的下标因为需要 set,必须写完整的 get/set 结构。
| 形式 | 写法 | 是否可赋值 |
|---|---|---|
| 只读下标 | subscript(i: Int) -> T { ... } | 否 |
| 读写下标 | subscript(i: Int) -> T { get { ... } set { ... } } | 是 |
| 自定义新值名 | subscript(i: Int) -> T { get { ... } set(v) { ... } } | 是 |
注意:只读下标不能出现在赋值语句左边。对只读下标写
instance[0] = x会编译报错。
类型下标 static subscript
和类型方法类似,下标也可以用 static 声明成类型下标,直接通过类型本身访问,不需要创建实例。
实例
enum Planet: Int {
case mercury = 1, venus, earth, mars
// 类型下标:通过类型本身访问,不需要创建实例
static subscript(n: Int) -> Planet {
return Planet(rawValue: n)!
}
}
let mars = Planet[4]
print(mars)
print(Planet[2] == .venus)
运行结果:
mars true
类中的类型下标用 class 声明时允许子类重写,用 static 声明则不允许重写。
下标与 inout
下标表达式是一个可读可写的位置,因此可以直接作为 inout 参数传入函数,函数内部对参数的修改会写回原来的元素。
实例
var numbers = [1, 2, 3, 4, 5]
// 下标表达式可以直接作为 inout 参数传入
func double(_ value: inout Int) {
value *= 2
}
double(&numbers[2])
print(numbers)
// swapAt 内部也是用下标读写元素
numbers.swapAt(0, 4)
print(numbers)
运行结果:
[1, 2, 6, 4, 5] [5, 2, 6, 4, 1]
如果下标只有 get 没有 set,把它作为 inout 参数传入会编译报错,因为函数无法把修改写回去。
常见问题
下标用起来简单,但下面几个坑值得留意。
下标越界会直接崩溃
数组下标不做边界检查之外的补救,访问超出范围的索引会触发运行时错误。自定义下标时建议在内部先判断索引是否合法。
字典下标返回的是可选值
字典下标取值的结果是可选类型,键不存在时返回 nil,所以不能直接当普通值使用,需要解包。
下标不能有默认参数
下标可以有多个参数,但不支持给参数设置默认值,重载要靠参数类型和个数来区分。
