下标subscript
本教程共 93 篇 · 第 54 篇 · 更新于 2026-08-08 · 约 8 分钟阅读
本节目标:读完你能用 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。掌握它,你的自定义类型就能拥有和数组、字典一样自然的访问体验。到此,枚举与结构体模块告一段落。