Link Search Menu Expand Document

Індекси

Класи, структури та перечислення можуть визначати індекси, що є скороченим синтаксисом для доступу до елементів колекції, списку чи послідовності. Індекси використовуються для присвоєння чи отримання значення за індексом без потреби у створенні окремих методів для присвоєння чи отримання. Наприклад, коли ви звертаєтесь до елементів масиву Array як someArray[index], або до елементу в словнику Dictionary як someDictionary[key], ви користуєтесь індексами.

Можна створювати кілька різних індексів для одного типу, і потрібна версія індексу буди вибрана, базуючись на типі значення, що передається в індекс. Індекси не обмежені одним виміром, можна створювати індекси, що приймають декілька вхідних параметрів, щоб відповідати потребам вашого власного типу.

Синтаксис індексації

Індекси дозволяють робити запити до екземплярів типу, записуючи одне чи більше значень у квадратних дужках після імені екземпляру. Їх синтаксис подібний одночасно до синтаксису методів екземпляру та властивостей, що обчислюються. Оголошення індексу починається із ключового слова subscript, після чого вказуються один чи кілька вхідних параметрів та тип, що повертається, так само, як і в методах екземпляру. На відміну від методів екземпляру, індекси бувають двох видів: для читання й запису, та тільки для читання. Така поведінка передається гетером і сетером, аналогічно до властивостей, що обчислюються:

subscript(index: Int) -> Int {
    get {
        // відповідне значення індексу повертаєтсья тут
    }
    set(newValue) {
        // присвоєння нового значення відбувається тут
    }
}

Тип параметра newValue співпадає із типом значення, що повертає індекс. Як і у властивостях, що обчислюються, можна не вказувати параметр сетера (newValue). В такому разі до сетера неявно передаватиметься параметр з іменем за замовчанням newValue.

Як і з властивостями тільки для читання, що обчислюються, можна пропустити ключове слово get:

subscript(index: Int) -> Int {
    // return an appropriate subscript value here
}

Ось приклад реалізації індексу тільки для читання, що дозволяє структурі TimesTable представляти таблицю множення на довільне ціле число:

struct TimesTable {
    let multiplier: Int
    subscript(index: Int) -> Int {
        return multiplier * index
    }
}
let threeTimesTable = TimesTable(multiplier: 3)
print("шість помножити на три дорівнює \(threeTimesTable[6])")
// Надрукує "шість помножити на три дорівнює 18"

У даному прикладі створюється новий екземпляр структури TimesTable для представлення таблиці множення на три. Це задається передачею значення 3 до ініціалізатора структури TimesTable як значення для ініціалізації властивості multiplier.

Тепер можна робити запити до екземпляра threeTimesTable шляхом звернення до індексу, як показано у виклику threeTimesTable[6]. У цьому виклику запитується шостий рядок у таблиці множення на три, що повертає значення 18, або 3 помножити на 6.

Примітка

Таблиця множення на ціле число базується на фіксованому математичному правилі, тому недоречно присвоювати threeTimesTable[someIndex] нове значення, і тому даний індекс в TimesTable було оголошено як індекс тільки для читання.

Використання індексів

Точний зміст “індексу” залежить від контексту, в якому його використовують. Індекси, як правило, застосовуються як скорочений синтаксис для доступу до елементів у колекції, списку чи послідовності. Ви можете реалізовувати індекси у найбільш властивий для функціональності кожного конкретного класу чи структури спосіб.

Наприклад, тип Dictionary у Swift реалізовує індекс для присвоєння та доступу до значень, що зберігаються в екземплярі Dictionary. Можна задати значення у словнику, вказавши ключ відповідного типу у квадратних дужках індексу, та присвоївши значення відповідного типу:

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

У прикладі вище визначено змінну на ім’я numberOfLegs, яку ініціалізовано літералом словника, що містить три пари ключ-значення. Тип словника numberOfLegs визначено компілятором як [String: Int]. Після створення словника, у прикладі використовується індекс для додавання до словника ключа "bird" типу String та відповідного йому значення 2 типу Int.

Детальнішу інформацію про індексацію словників можна знайти у підрозділі Доступ до елементів словника та його модифікація.

Примітка

У Swift тип Dictionary реалізовує індексацію ключ-значення за допомогою індексу, що приймає та повертає опціональний тип. У словнику numberOfLegs вище, індекс приймає та повертає значення типу Int?, або “опціональний int”. Тип Dictionary використовує опціональний тип індекса для моделювання факту, що не кожен ключ має значення, і для можливості видаляти значення для заданого ключа шляхом присвоєння значення nil для цього ключа.

Опції індексів

Індекси можуть мати будь-яку кількість вхідних параметрів, і ці вхідні параметри можуть мати будь-який тип. Індекси також можуть повертати будь-який тип.

Як і функції, індекси можуть мати варіативні параметри, а параметри можуть мати значення за замовчанням, як описано у відповідних розділах Варіативні функції та Значення параметрів за замовчуванням. Однак, на відміну від функцій, індекси не можуть мати двонаправлених параметрів.

Клас чи структура може містити стільки реалізації індекси, скільки йому або їй потрібно; при цьому потрібна реалізація визначиться компілятором за типом значення чи значень, що містяться у квадратних дужках індексу в момент використання індексу. Ця можливість існування кількох індексів називається перевантаженням індексів.

Хоча найчастіше індекси приймають єдиний параметр, можна створювати індекси із кількома параметрами, якщо це підходить для даного типу. У наступному прикладі оголошено структуру 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(repeating: 0.0, count: rows * columns)
    }
    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
        }
    }
}

Структура Matrix містить ініціалізатор, що приймає два параметри rows та columns (кількість рядків та стовпчиків відповідно), і створює масив, достатньо великий для зберігання rows * columns значень типу Double. Кожна позиція в матриці отримує початкове значення 0.0. Щоб досягнути цього, розмір масиву та початкове значення 0.0 передаються в ініціалізатор масиву, котрий створює та ініціалізує новий масив відповідного розміру. Цей ініціалізатор детальніше описано в підрозділі Створення масиву зі значенням за замовчанням.

Тепер можна створити новий екземпляр структури Matrix, передавши потрібну кількість рядків та стовпчиків в її ініціалізатор:

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

У попередньому прикладі створено новий екземпляр структури Matrix із двома рядочками та двома стовпчиками. Масив grid у цьому екземплярі фактично містить пласку розгортку матриці, ніби її вміст було прочитано зверху вниз зліва направо:

Значення у матриці можна задати, передаючи рядок та стовпчик до індексу, розділивши їх комою:

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

Ці дві інструкції викликають сетер індексу, і задають значення 1.5 у верхній правій позиції матриці (де row дорівнює 0 та column дорівнює 1), та значення 3.2 у нижній лівій позиції (де row дорівнює 1 та column дорівнює 0):

Сетер та гетер індексу структури Matrix містять assert, котрий перевіряє, що передані параметри row та column є коректними. Щоб спростити ці перевірки assert, структура Matrix містить метод для зручності, що називається indexIsValid(row:column:). Цей метод перевіряє, чи є задані row та column в межах розмірів матриці:

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

Якщо спробувати звернутись до індексу за межами розмірів матриці, спрацює assert:

let someValue = matrix[2, 2]
// Цей код спровокує assert, оскільки [2, 2] лежить за межами розмірів матриці.

Індекси типів

Описані вище індекси є індексами екземплярів, тобто індекси, які можна викликати на конкретному екземплярі певного типу. На додачу до них, можна оголошувати індекси, які можна викликати на самому типі. Цей різновид індексів називається індексом типу. Індекси типів оголошуються за допомогою ключового слова static перед ключовим словом subscript. У класах можна також використовувати ключове слово class замість 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]
print(mars)