首页 / Swift 编程语言教程 / 下标subscript

Swift 编程语言教程

下标subscript

本教程共 93 篇 · 第 54 篇 · 更新于 2026-08-08 · 约 8 分钟阅读

SwiftSwift 编程语言教程下标subscript类型下标下标重载读写下标

本节目标:读完你能用 subscript 给结构体(或类、枚举)添加方括号访问语法,实现读写与多参数下标。

1-1 下标是什么

你一定写过 array[0]dict["key"]。方括号取值这套语法,在 Swift 里叫”下标”(subscript)。它本质是访问元素的快捷方式,省去单独的 get/set 方法。自己定义的类型也能拥有下标,让自定义类型的访问体验和数组、字典一样自然。

下标的价值在于”语法糖”:同样是取一个值,写 myType[3] 比写 myType.getValue(at: 3) 简洁太多,也更贴合”按位置取东西”的直觉。

1-2 下标语法

下标写法介于实例方法和计算属性之间:用 subscript 关键字,声明参数和返回类型,配 getter/setter:

struct TimesTable {
    let multiplier: Int
    subscript(index: Int) -> Int {
        return multiplier * index
    }
}
let threeTimes = TimesTable(multiplier: 3)
print("6 乘 3 是 \(threeTimes[6])")  // 18

下标没有名字,靠方括号里的参数调用。只读下标可省掉 get:

subscript(index: Int) -> Int {
    // 返回合适的值
}

setter 的新值默认也叫 newValue,类型与返回类型一致。从结构上看,下标就是一个”没有名字、用方括号触发”的计算属性。

1-3 读写下标

下标可以既能读也能写。比如给一个自定义集合加下标,让它支持赋值:

struct DailyTemp {
    var values = [0, 0, 0, 0]
    subscript(hour: Int) -> Int {
        get { return values[hour] }
        set { values[hour] = newValue }
    }
}
var temp = DailyTemp()
temp[2] = 25
print(temp[2])  // 25

temp[2] = 25 走 setter,temp[2] 走 getter。用起来和数组一模一样。读写下标让自定义容器既能被查询又能被修改,是最常用的下标形态。

1-4 多参数下标

下标不限于一个参数,也可是任意类型。二维矩阵用”行、列”两个参数最直观:

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)
    }
    subscript(row: Int, column: Int) -> Double {
        get {
            return grid[(row * columns) + column]
        }
        set {
            grid[(row * columns) + column] = newValue
        }
    }
}
var m = Matrix(rows: 2, columns: 2)
m[0, 1] = 1.5
print(m[0, 1])  // 1.5

调用时多个参数用逗号隔开:m[0, 1]。一个类型还能定义多个下标(下标重载),编译器按参数类型选最合适的。比如同一个类型可以同时提供 subscript(index: Int)subscript(key: String),调用时按你传的参数类型自动分流。

Note

下标不能用 in-out 参数,但能带默认参数、可变参数。下标和函数一样灵活,只是入口是方括号。需要”进出都改原值”的场景不适合用下标,老老实实写方法更清楚。

1-5 类型下标

下标也能属于类型本身,用 static 标记。比如按编号取行星:

enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
    static subscript(n: Int) -> Planet {
        return Planet(rawValue: n)!
    }
}
let mars = Planet[4]

类型下标通过类型名加方括号调用,不依赖实例。它适合那种”按某个键查一个全局共享的东西”的场景,和类型属性、类型方法的定位一致。

1-6 下标设计的几点提醒

下标应当”顾名思义”:方括号里放的是”索引/键”,返回的是”那个位置的东西”。别把下标写成一个藏了复杂副作用的黑洞,否则调用方会误以为它只是取值。只读下标用于”查”,读写下标用于”容器式”访问,多参数下标用于”多维”数据,类型下标用于”全局查表”。分清楚这几类,你的 API 才清晰。

1-7 下标重载的实际例子

除了二维矩阵,下标重载还能做”按不同键查同一容器”。比如一个通讯录,既支持 book["张三"] 按名字查,也支持 book[123] 按编号查——只要定义两个 subscript,一个收 String、一个收 Int,编译器会自动选。这让同一个类型在不同语境下都有顺手的访问方式。

重载时两个下标的返回类型也可以不同:按名字查可能返回”联系人?“(因为名字可能不存在),按编号查可能返回”联系人”(编号保证有效)。返回类型随参数类型变化,正是下标灵活的体现。

1-8 下标与数组字典的异同

你自定义的下标,语义上和 Array、Dictionary 的下标一样,但行为由你定义。Array 越界会崩溃,Dictionary 返回可选——这两种风格你都能模仿:需要”键不一定存在”就返回可选类型;需要”必须存在”就直接返回,并在越界时自行处理(如断言)。

明确你的下标”查不到时怎么办”,是设计好它的关键。一个稳健的下标应当让调用方清楚知道:传非法索引会得到什么(nil、崩溃、还是被修正),不该让行为含糊不清。

1-9 下标里做越界保护

前面 Matrix 用了 assert 在越界时崩溃。实际写库时,你可能更想”温柔处理”:比如越界返回默认值或可选。把下标返回类型改成可选,调用方就必须解包,强迫他面对”可能查不到”:

struct SafeMatrix {
    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)
    }
    subscript(row: Int, column: Int) -> Double? {
        guard row >= 0 && row < rows && column >= 0 && column < columns else {
            return nil
        }
        return grid[(row * columns) + column]
    }
}

是否返回可选、是否崩溃,取决于你的设计意图。关键是:下标行为要可预期,调用方一看就知道规则。

1-10 下标不是只能查集合

虽然下标最常见于”按索引查元素”,但它本质是”方括号触发的一段 get/set 逻辑”,未必背后有数组。比如一个”配置”结构体,用 config["theme"] 返回一个值,内部可能是查字典、也可能现算,调用方不关心。只要是”给个键、还个值”的语义,下标都比方法调用更自然。

别被”下标=集合”的印象限制住。任何”通过某种索引取东西”的场景,都可以考虑下标,只要它的行为和名字契合、不会误导调用方。

1-11 下标与计算属性的关系

下标本质上和计算属性很像:都是用 get/set 暴露一段逻辑,只是触发方式不同——计算属性用点语法(obj.value),下标用方括号(obj[key])。所以你会发现,能写成计算属性的东西,往往也能写成下标,区别只在”调用姿势”。

选择标准很直观:访问的是”按某个键/索引定位的元素”,用下标;访问的是”对象的一个具名特征”,用计算属性。顺着这条线走,API 的形态就会自然、好记。

1-12 小结

下标(subscript)用方括号访问元素,是 get/set 的语法糖。只读可省 get,读写配 setter。下标支持多参数与重载,类型下标用 static。掌握它,你的自定义类型就能拥有和数组、字典一样自然的访问体验。到此,枚举与结构体模块告一段落。