现在位置: 首页 > Swift 教程 > 正文

Swift 下标

下标(subscript)是访问集合、序列或自定义类型中元素的快捷方式,用方括号语法 实例[索引] 取值和赋值。

数组的 array[0]、字典的 dict["key"] 都是下标,你可以在自己的类、结构体或枚举里定义同样的语法。


下标语法与应用

定义下标用 subscript 关键字,写法介于实例方法和计算属性之间:有参数列表和返回类型,但没有名字。

实例

import Foundation

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 分别处理取值与赋值。

实例

import Foundation

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) 自定义名字。

下标可以接收任意数量的参数,参数类型也不受限制,因此同一个类型可以提供多个下标实现,靠参数类型和个数来区分,这就是下标的重载。

实例

import Foundation

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 声明成类型下标,直接通过类型本身访问,不需要创建实例。

实例

import Foundation

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 参数传入函数,函数内部对参数的修改会写回原来的元素。

实例

import Foundation

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,所以不能直接当普通值使用,需要解包。

下标不能有默认参数

下标可以有多个参数,但不支持给参数设置默认值,重载要靠参数类型和个数来区分。