Swift 下標指令碼(Subscripts)

ZY_FlyWay發表於2018-06-15

本頁包含內容:

下標指令碼 可以定義在類(Class)、結構體(structure)和列舉(enumeration)這些目標中,可以認為是訪問集合(collection),列表(list)或序列(sequence的快捷方式,使用下標指令碼的索引設定和獲取值,不需要再呼叫例項的特定的賦值和訪問方法。舉例來說,用下標指令碼訪問一個陣列(Array)例項中的元素可以這樣寫 someArray[index] ,訪問字典(Dictionary)例項中的元素可以這樣寫 someDictionary[key]

對於同一個目標可以定義多個下標指令碼,通過索引值型別的不同來進行過載,下標指令碼不限於單個緯度,你可以定義多個入參的下標指令碼滿足自定義型別的需求。

譯者:這裡附屬指令碼過載在本小節中原文並沒有任何演示 

下標指令碼語法

下標指令碼允許你通過在例項後面的方括號中傳入一個或者多個的索引值來對例項進行訪問和賦值。語法類似於例項方法和計算型屬性的混合。與定義例項方法類似,定義下標指令碼使用subscript關鍵字,顯式宣告入參(一個或多個)和返回型別。與例項方法不同的是下標指令碼可以設定為讀寫或只讀。這種方式又有點像計算型屬性的getter和setter:

subscript(index: Int) -> Int {
    get {
      // 返回與入參匹配的Int型別的值
    }

    set(newValue) {
      // 執行賦值操作
    }
}

newValue的型別必須和下標指令碼定義的返回型別相同。與計算型屬性相同的是set的入參宣告newValue就算不寫,在set程式碼塊中依然可以使用預設的newValue這個變數來訪問新賦的值。

與只讀計算型屬性一樣,可以直接將原本應該寫在get程式碼塊中的程式碼寫在subscript中:

subscript(index: Int) -> Int {
    // 返回與入參匹配的Int型別的值
}

下面程式碼演示了一個在TimesTable結構體中使用只讀下標指令碼的用法,該結構體用來展示傳入整數的n倍。

struct TimesTable {
    let multiplier: Int
    subscript(index: Int) -> Int {
      return multiplier * index
    }
}
let threeTimesTable = TimesTable(multiplier: 3)
print("3的6倍是\(threeTimesTable[6])")
// 輸出 "3的6倍是18"

在上例中,通過TimesTable結構體建立了一個用來表示索引值三倍的例項。數值3作為結構體建構函式入參初始化例項成員multiplier

你可以通過下標指令碼來得到結果,比如threeTimesTable[6]。這條語句訪問了threeTimesTable的第六個元素,返回63倍即18

注意:
TimesTable例子是基於一個固定的數學公式。它並不適合對threeTimesTable[someIndex]進行賦值操作,這也是為什麼附屬指令碼只定義為只讀的原因。 

下標指令碼用法

根據使用場景不同下標指令碼也具有不同的含義。通常下標指令碼是用來訪問集合(collection),列表(list)或序列(sequence)中元素的快捷方式。你可以在你自己特定的類或結構體中自由的實現下標指令碼來提供合適的功能。

例如,Swift 的字典(Dictionary)實現了通過下標指令碼來對其例項中存放的值進行存取操作。在下標指令碼中使用和字典索引相同型別的值,並且把一個字典值型別的值賦值給這個下標指令碼來為字典設值:

var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
numberOfLegs["bird"] = 2

上例定義一個名為numberOfLegs的變數並用一個字典字面量初始化出了包含三對鍵值的字典例項。numberOfLegs的字典存放值型別推斷為[String:Int]。字典例項建立完成之後通過下標指令碼的方式將整型值2賦值到字典例項的索引為bird的位置中。

更多關於字典(Dictionary)下標指令碼的資訊請參考讀取和修改字典

注意:
Swift 中字典的附屬指令碼實現中,在get部分返回值是Int?,上例中的numberOfLegs字典通過附屬指令碼返回的是一個Int?或者說“可選的int”,不是每個字典的索引都能得到一個整型值,對於沒有設過值的索引的訪問返回的結果就是nil;同樣想要從字典例項中刪除某個索引下的值也只需要給這個索引賦值為nil即可。 

下標指令碼選項

下標指令碼允許任意數量的入參索引,並且每個入參型別也沒有限制。下標指令碼的返回值也可以是任何型別。下標指令碼可以使用變數引數和可變引數,但使用寫入讀出(in-out)引數或給引數設定預設值都是不允許的。

一個類或結構體可以根據自身需要提供多個下標指令碼實現,在定義下標指令碼時通過入參的型別進行區分,使用下標指令碼時會自動匹配合適的下標指令碼實現執行,這就是下標指令碼的過載

一個下標指令碼入參是最常見的情況,但只要有合適的場景也可以定義多個下標指令碼入參。如下例定義了一個Matrix結構體,將呈現一個Double型別的二維矩陣。Matrix結構體的下標指令碼需要兩個整型引數:


struct Matrix {
    let rows: Int, columns: Int
    var grid: [Double]
    init(rows: Int, columns: Int) {
      self.rows = rows
      self.columns = columns
      grid = Array(count: rows * columns, repeatedValue: 0.0)
    }
    func indexIsValidForRow(row: Int, column: Int) -> Bool {
        return row >= 0 && row < rows && column >= 0 && column < columns
    }
    subscript(row: Int, column: Int) -> Double {
        get {
            assert(indexIsValidForRow(row, column: column), "Index out of range")
            return grid[(row * columns) + column]
        }
        set {
            assert(indexIsValidForRow(row, column: column), "Index out of range")
            grid[(row * columns) + column] = newValue
        }
    }
}

Matrix提供了一個兩個入參的構造方法,入參分別是rowscolumns,建立了一個足夠容納rows * columns個數的Double型別陣列。通過傳入陣列長度和初始值0.0到陣列的一個構造器,將Matrix中每個元素初始值0.0。關於陣列的構造方法和析構方法請參考建立一個空陣列

你可以通過傳入合適的rowcolumn的數量來構造一個新的Matrix例項:

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

上例中建立了一個新的兩行兩列的Matrix例項。在閱讀順序從左上到右下的Matrix例項中的陣列例項grid是矩陣二維陣列的扁平化儲存:

// 示意圖
grid = [0.0, 0.0, 0.0, 0.0]

      col0  col1
row0   [0.0,     0.0,
row1    0.0,  0.0]

將值賦給帶有rowcolumn下標指令碼的matrix例項表示式可以完成賦值操作,下標指令碼入參使用逗號分割

matrix[0, 1] = 1.5
matrix[1, 0] = 3.2

上面兩條語句分別讓matrix的右上值為 1.5,坐下值為 3.2:

[0.0, 1.5,
 3.2, 0.0]

Matrix下標指令碼的gettersetter中同時呼叫了下標指令碼入參的rowcolumn是否有效的判斷。為了方便進行斷言,Matrix包含了一個名為indexIsValidForRow(_:column:)的成員方法,用來確認入參的rowcolumn值是否會造成陣列越界:

func indexIsValidForRow(row: Int, column: Int) -> Bool {
    return row >= 0 && row < rows && column >= 0 && column < columns
}

斷言在下標指令碼越界時觸發:

let someValue = matrix[2, 2]
// 斷言將會觸發,因為 [2, 2] 已經超過了matrix的最大長度

相關文章