Swift

2023 年 10 月 12 日

Swift 用索引取值的 Subscripts 下標語法

已複製到剪貼板


Swift 的 Subscripts 下標可以讓我們自己實作像是 Array 或 Dictionary 用中括號來取值的語法。

Swift Subscripts

Array 與 Dictionary 的 Subscripts 下標語法

我們最常見也習慣的 Subscripts 下標語法,就出現在要存取 Array 中的某個元素,或是要透過 key 來存取 Dictionary 中的某個值時,如 someArray[index]Dictionary[key],像這樣使用中括號並透過索引的方式來存取值,就是所謂的「Subscripts 下標語法」。

Array 與 Dictionary 之所以能直接在後方使用中括號,就是因為它們都有實作 Subscripts 下標,這能讓我們用較精簡的方式來取值或設定值,而非透過 get 與 set 的 func。而其實,我們也能為自己定義的型別實作 Subscripts 下標。

定義 Swift 的 Subscripts 下標語法

class、struct 和 enum 都能定義 Subscripts 下標,而且也能定義不只一個,就像 func 可以 overload 重載一樣。除了可以定義多個 Subscripts 下標,每個下標也能吃超過一個輸入!

可讀可寫的 Subscripts 下標

定義 Subscripts 的寫法和定義 computed property 類似:

subscript(index: Int) -> Int {
    get {
        // 回傳特定的值
    }
    set(newValue) {
        // 執行特定的資料設定
    }
}

我們會使用 subscript 這個關鍵字,並決定要有幾個輸入、回傳什麼,就像定義 func 一樣。但內容跟 func 不一樣,Subscripts 可以決定要「read-write 可讀可寫」或是「read-only 僅可讀」,就像 computed property 有 getter 與 setter 一樣,而這裡的 setter 也是可以在小括號裡幫 newValue 取名字,如果不想取的話,則可以省略小括號,並使用預設的「newValue」。

僅可讀的 Subscripts 下標

如果是定義僅可讀 read-only 的 Subscripts 下標,可以省略 getset

subscript(index: Int) -> Int {
    // 回傳特定的值
}

這裡舉一個實際使用 Subscripts 下標的例子:

struct TimesTable {
    /// 乘數
    let multiplier: Int
    /// index 為被乘數
    subscript(index: Int) -> Int {
        return index * multiplier
    }
}
let threeTimesTable = TimesTable(multiplier: 3)
print("6 乘以 3 等於 \(threeTimesTable[6])")

在型別裡定義了 Subscripts 之後,我們就能直接在變數後面使用中括號來呼叫 subscript

注意

這裡可以發現,我們的 Subscripts 定義了參數名稱「index」,但和 func 不一樣,在使用中括號時,不需要指名參數名稱,可以直接傳入索引。

自動省略 Argument 的 Subscripts

因為 Swift 的 Subscripts 會自動省略 Argument,所以上面的例子相當於:

subscript(_ index: Int) -> Int {
    return index * multiplier
}

而如果在使用中括號時,希望能看到 Argument 的話,則可以另外命名:

subscript(multiplicand index: Int) -> Int {
    return index * multiplier
}

使用上就會像這樣:

let threeTimesTable = TimesTable(multiplier: 3)
print("6 乘以 3 等於 \(threeTimesTable[multiplicand: 6])")

備註

我們有另外一篇文章詳細說明了 Func 中,Argument 與 Parameter 的差異。如果想了解更多的話,可以點擊這裡

Swift Dictionary 的 Subscripts 實作邏輯

Swift Dictionary 用 Subscripts 讓我們可以用 key-value 的方式來存取資料。在 get 的實作上,Swift 會回傳 Optional,因為我們並不能保證每個 key 值都有對應的 value。而刪除時,就是把 key 對應的 value 改成 nil

多個輸入的 Subscripts 下標

雖然平常看到的 Subscripts 下標都是只吃一個輸入而已,但我們仍可以根據需求,實作多個輸入的 Subscripts。就像 func 一樣,可以有多個輸入、不同的輸入型別、不同的回傳型別,但跟 func 唯一不一樣的是不能使用 inout 參數。

這裡用 Subscripts 實作一個吃兩個輸入的二維陣列:

/// 二維陣列
struct Matrix {
    /// 行與列
    let rows: Int, columns: Int
    /// 裝所有元素的 Array
    var grid: [Double]
    init(rows: Int, columns: Int) {
        self.rows = rows
        self.columns = columns
        // 陣列剛建出來時,各個元素的預設值為 0.0
        grid = Array(repeating: 0.0, count: rows * columns)
    }
    /// 欲存取的 Index 是否有效
    /// - Parameters:
    ///   - row: 欲存取 Index 的行
    ///   - column: 欲存取 Index 的列
    /// - Returns: 是否有效
    func indexIsValid(row: Int, column: Int) -> Bool {
        return row >= 0 && row < rows && column >= 0 && column < columns
    }
    subscript(row: Int, column: Int) -> Double {
        get {
            assert(indexIsValid(row: row, column: column), "Index out of range")
            return grid[(row * columns) + column]
        }
        set {
            assert(indexIsValid(row: row, column: column), "Index out of range")
            grid[(row * columns) + column] = newValue
        }
    }
}

因為這是一個二維陣列,所以定義了一個能吃兩個輸入的 Subscripts,來讓我們指定要存取哪一行哪一列的元素。

在使用這個二維陣列 Matrix 之前,就要先建一個實體。

var matrix = Matrix(rows: 2, columns: 2)

這裡建一個 2×2 的二維陣列,當我們想透過 Subscripts 下標來存取值時,會這樣寫:

matrix[0, 1] = 1.5
matrix[1, 0] = 3.2
let someValue = matrix[2, 2] // 這會觸發 assert:Index out of range

當 Subscripts 有兩個以上的輸入時,會寫在同一個中括號裡,而非多個中括號。

型別 Subscripts

和常數、變數、方法一樣,如果我們想直接在型別上使用下標語法,而不需先建一個實體的話,也能使用關鍵字 static

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

分享文章

已複製到剪貼板

主題文章

查看 Swift

超級感謝

關於 XcodeProject

XcodeProject 創立於 2023,致力於協助開發者探索 Apple 的創新世界,學習在 iOS、iPadOS、macOS、tvOS、visionOS 與 watchOS 上開發 App,發現眾多技術與框架,讓開發者獲得更多能力。


Contacts

Ricky Chuang

XcodeProject

RickyChuang.xcodeproj@gmail.com

XcodeProject 聯絡

contact.xcodeproj@gmail.com

最新文章