10.下标

99 阅读5分钟

下标

目录

  1. 下标的基本语法
  2. 下标的使用
  3. 下标的重载
  4. 结构体、类的下标
  5. 接收多个参数的下标
  6. 下标的应用场景

下标的基本语法

下标(Subscript)允许你通过在实例名称后面的方括号中传入一个或多个索引值来对实例进行查询。

基本语法结构

subscript(index: Int) -> Int {
    get {
        // 返回一个适当的 Int 类型的值
    }
    set(newValue) {
        // 执行适当的赋值操作
    }
}

只读下标

subscript(index: Int) -> Int {
    // 返回一个适当的 Int 类型的值
}

下标的使用

基本示例

struct TimesTable {
    let multiplier: Int
    
    subscript(index: Int) -> Int {
        return multiplier * index
    }
}

let threeTimesTable = TimesTable(multiplier: 3)
print(threeTimesTable[6])  // 18

可读写下标

struct Matrix {
    var data: [[Int]]
    let rows: Int
    let columns: Int
    
    init(rows: Int, columns: Int) {
        self.rows = rows
        self.columns = columns
        data = Array(repeating: Array(repeating: 0, count: columns), count: rows)
    }
    
    subscript(row: Int, column: Int) -> Int {
        get {
            assert(row >= 0 && row < rows, "Row index out of range")
            assert(column >= 0 && column < columns, "Column index out of range")
            return data[row][column]
        }
        set {
            assert(row >= 0 && row < rows, "Row index out of range")
            assert(column >= 0 && column < columns, "Column index out of range")
            data[row][column] = newValue
        }
    }
}

var matrix = Matrix(rows: 2, columns: 2)
matrix[0, 1] = 1
matrix[1, 0] = 2
print(matrix[0, 1])  // 1
print(matrix[1, 0])  // 2

下标的重载

可以根据不同的参数类型和数量来重载下标。

参数类型重载

struct StringContainer {
    var items: [String] = []
    
    // 通过索引访问
    subscript(index: Int) -> String {
        get {
            return items[index]
        }
        set {
            items[index] = newValue
        }
    }
    
    // 通过字符串查找
    subscript(key: String) -> String? {
        get {
            return items.first { $0.contains(key) }
        }
        set {
            if let index = items.firstIndex(where: { $0.contains(key) }) {
                if let newValue = newValue {
                    items[index] = newValue
                } else {
                    items.remove(at: index)
                }
            } else if let newValue = newValue {
                items.append(newValue)
            }
        }
    }
}

var container = StringContainer()
container.items = ["apple", "banana", "cherry"]

print(container[0])        // "apple"
print(container["ban"])    // Optional("banana")

参数数量重载

struct Dictionary2D<T> {
    private var data: [[T?]]
    
    init(rows: Int, columns: Int) {
        data = Array(repeating: Array(repeating: nil, count: columns), count: rows)
    }
    
    // 单个索引访问行
    subscript(row: Int) -> [T?] {
        get {
            return data[row]
        }
        set {
            data[row] = newValue
        }
    }
    
    // 两个索引访问具体元素
    subscript(row: Int, column: Int) -> T? {
        get {
            return data[row][column]
        }
        set {
            data[row][column] = newValue
        }
    }
}

结构体、类的下标

结构体下标

struct Point {
    var x = 0, y = 0
    
    subscript(index: Int) -> Int {
        get {
            switch index {
            case 0: return x
            case 1: return y
            default: fatalError("Index out of range")
            }
        }
        set {
            switch index {
            case 0: x = newValue
            case 1: y = newValue
            default: fatalError("Index out of range")
            }
        }
    }
}

var point = Point()
point[0] = 10
point[1] = 20
print(point[0])  // 10
print(point[1])  // 20

类下标

class PointManager {
    var point = Point()
    
    subscript(index: Int) -> Point {
        get {
            return point
        }
        set {
            point = newValue
        }
    }
}

let pm = PointManager()
print(pm[0])  // Point(x: 0, y: 0)

接收多个参数的下标

下标可以接收多个参数,这对于多维数据结构非常有用。

二维网格示例

class Grid {
    var data = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8]
    ]
    
    subscript(row: Int, column: Int) -> Int {
        get {
            guard row >= 0 && row < 3 && column >= 0 && column < 3 else {
                return 0
            }
            return data[row][column]
        }
        set {
            guard row >= 0 && row < 3 && column >= 0 && column < 3 else {
                return
            }
            data[row][column] = newValue
        }
    }
}

var grid = Grid()
grid[0, 1] = 77
grid[1, 2] = 88
grid[2, 0] = 99
print(grid.data)  // [[0, 77, 2], [3, 4, 88], [99, 7, 8]]

三维空间示例

struct Space3D {
    private var data: [[[Int]]]
    
    init(x: Int, y: Int, z: Int) {
        data = Array(repeating: Array(repeating: Array(repeating: 0, count: z), count: y), count: x)
    }
    
    subscript(x: Int, y: Int, z: Int) -> Int {
        get {
            return data[x][y][z]
        }
        set {
            data[x][y][z] = newValue
        }
    }
}

var space = Space3D(x: 2, y: 2, z: 2)
space[0, 1, 1] = 42
print(space[0, 1, 1])  // 42

下标的应用场景

1. 字典风格的访问

struct Configuration {
    private var settings: [String: Any] = [:]
    
    subscript(key: String) -> Any? {
        get {
            return settings[key]
        }
        set {
            settings[key] = newValue
        }
    }
}

var config = Configuration()
config["theme"] = "dark"
config["fontSize"] = 16
print(config["theme"])  // Optional("dark")

2. 数组的安全访问

extension Array {
    subscript(safe index: Int) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

let numbers = [1, 2, 3, 4, 5]
print(numbers[safe: 2])   // Optional(3)
print(numbers[safe: 10])  // nil

3. 范围访问

extension Array {
    subscript(range: Range<Int>) -> ArraySlice<Element> {
        return self[range]
    }
}

let array = [1, 2, 3, 4, 5]
print(array[1..<4])  // [2, 3, 4]

4. 函数式访问

struct FunctionContainer {
    var functions: [String: () -> Void] = [:]
    
    subscript(name: String) -> (() -> Void)? {
        get {
            return functions[name]
        }
        set {
            functions[name] = newValue
        }
    }
}

var container = FunctionContainer()
container["greet"] = { print("Hello!") }
container["greet"]?()  // "Hello!"

5. 类型转换下标

struct FlexibleArray {
    private var data: [Any] = []
    
    subscript<T>(index: Int, as type: T.Type) -> T? {
        guard index >= 0 && index < data.count else { return nil }
        return data[index] as? T
    }
    
    func append(_ item: Any) {
        data.append(item)
    }
}

var flexArray = FlexibleArray()
flexArray.append("Hello")
flexArray.append(42)
flexArray.append(3.14)

let string: String? = flexArray[0, as: String.self]  // "Hello"
let number: Int? = flexArray[1, as: Int.self]        // 42
let double: Double? = flexArray[2, as: Double.self]  // 3.14

总结

下标的特点

  1. 语法简洁:使用方括号语法,类似数组和字典
  2. 类型安全:支持泛型,编译时类型检查
  3. 灵活重载:可以根据参数类型和数量重载
  4. 读写控制:可以是只读或可读写
  5. 多参数支持:支持多个参数的下标

下标 vs 方法

特性下标方法
语法obj[key]obj.method(key)
用途访问集合元素执行操作
读写支持get/set需要分别定义
参数通常是索引/键任意参数
返回值通常是元素值任意返回值

使用建议

  1. 集合类型:为自定义集合类型提供下标访问
  2. 配置管理:使用字符串键访问配置项
  3. 多维数据:使用多参数下标访问多维数据
  4. 安全访问:提供安全的数组访问方式
  5. 语义清晰:下标应该表示"获取某个位置的元素"的语义

最佳实践

  • 下标应该有明确的语义,通常用于访问集合元素
  • 提供合适的错误处理和边界检查
  • 考虑提供只读和可读写两种版本
  • 合理使用下标重载,避免过度复杂化
  • 文档化下标的参数含义和返回值