Зачем нужен массив
Представьте список покупок на бумаге: молоко, хлеб, яйца — три отдельные строки, но вы держите их как одно целое и можете пройтись по каждой. Ровно так же в программе часто нужно хранить не одно значение, а упорядоченный набор однотипных значений: имена учеников, оценки за тест, координаты точек на графике. Заводить для этого отдельную переменную на каждое значение неудобно и не масштабируется. Для таких случаев в Swift есть Array — упорядоченная коллекция элементов одного типа.
Объявление и литерал массива
Самый частый способ создать массив — литерал в квадратных скобках:
let fruits = ["яблоко", "груша", "банан"]
let scores: [Int] = [90, 75, 100]
Тип массива записывается как [Element], где Element — тип элементов: [Int] означает «массив из Int». Это сокращённая запись; полная форма выглядит как Array<Int> — она означает ровно то же самое, но короткую форму со скобками используют почти всегда, а полную — когда явно нужно назвать сам тип Array, например в сообщениях об ошибках компилятора.
Если элементов пока нет, массив можно создать пустым — но тогда компилятору нужно явно сказать, элементы какого типа там будут:
var numbers: [Int] = []
var names = [String]()
Оба варианта создают пустой массив, просто с разным синтаксисом аннотации. Ещё один способ — заполнить массив одинаковыми значениями заданное число раз, для этого есть инициализатор Array(repeating:count:):
let zeros = Array(repeating: 0, count: 5)
// [0, 0, 0, 0, 0]
Это удобно, когда нужна заготовка фиксированного размера, которую вы потом заполните в цикле.
Доступ по индексу
Элементы массива нумеруются с нуля: первый элемент имеет индекс 0, второй — 1, и так до count - 1. Обратиться к элементу можно через квадратные скобки:
let colors = ["красный", "зелёный", "синий"]
print(colors[0]) // красный
print(colors[2]) // синий
Здесь colors[0] — первый элемент, а не второй, как можно было бы подумать по-бытовому. Это частый источник ошибки на единицу у новичков, особенно в циклах.
Если запросить индекс, которого в массиве нет — например colors[10] при трёх элементах, — программа не вернёт nil и не пропустит это молча: она аварийно завершится прямо в момент обращения. Компилятор не может проверить это заранее, потому что индекс часто вычисляется во время работы программы, поэтому ответственность за корректный индекс лежит на вас.
Безопасный доступ
Раз обращение по произвольному индексу опасно, у массива есть инструменты, чтобы проверять границы заранее или обходить их вовсе.
isEmpty—true, если элементов нет вообще; читается понятнее, чем сравнениеcount == 0.count— сколько элементов в массиве прямо сейчас.indices.contains(i)— проверяет, что индексiсуществует в массиве, перед тем как в него лезть.firstиlast— возвращают не сам элемент, аOptional: если массив пуст, вы получитеnilвместо аварийного завершения.
let queue = ["Аня", "Борис"]
if queue.indices.contains(5) {
print(queue[5])
} else {
print("такого места в очереди нет")
}
if let firstPerson = queue.first {
print("первый в очереди: \(firstPerson)")
}
Обратите внимание: queue.first имеет тип String?, поэтому его нужно развернуть — тем же if let, что уже встречался с опционалами.
Изменение массива
Менять содержимое можно только у массива, объявленного через var — let-массив после создания не меняется, как и любая другая константа.
var basket = ["хлеб", "молоко"]
basket.append("сыр") // добавить в конец
basket += ["масло", "яйца"] // добавить сразу несколько
basket.insert("вода", at: 0) // вставить по индексу
basket.remove(at: 1) // удалить по индексу
basket.removeLast() // удалить последний
// basket.removeAll() // удалить всё
append добавляет один элемент в конец, += — делает то же самое сразу для нескольких элементов из другого массива. insert(at:) и remove(at:) работают по индексу, поэтому здесь тоже легко выйти за границы — remove(at: 100) для маленького массива аварийно завершит программу так же, как и обращение по индексу на чтение.
Срезы
Диапазон индексов позволяет получить сразу часть массива — срез:
let letters = ["a", "b", "c", "d", "e"]
let middle = letters[1...3] // ["b", "c", "d"]
Результат такой операции — не обычный Array, а отдельный тип ArraySlice. Он ссылается на память исходного массива, а не копирует её сразу, поэтому срезы дешёвы в создании. Ловушка в том, что индексы у среза наследуются от исходного массива, а не начинаются заново с нуля: в примере выше middle[0] вызовет аварийное завершение, потому что у среза есть индексы 1, 2 и 3, но нет индекса 0. Если нужен обычный массив с индексами от нуля, срез оборачивают в Array(middle).
Обход массива
Для перебора элементов подходит уже знакомый for-in:
let marks = [4, 5, 3, 5]
var total = 0
for mark in marks {
total += mark
}
print(total) // 17
Здесь цикл проходит по каждому элементу массива по очереди, а не по индексам — mark сразу содержит значение, а не номер позиции.
Массив — тип-значение
Это, пожалуй, самое неожиданное для новичков свойство массивов в Swift. При присваивании массива другой переменной или передаче в функцию Swift не создаёт вторую ссылку на те же данные — он ведёт себя так, будто данные копируются:
var original = [1, 2, 3]
var copy = original
copy.append(4)
print(original) // [1, 2, 3]
print(copy) // [1, 2, 3, 4]
Изменение copy никак не отразилось на original — это два независимых массива, хотя copy и появился как присваивание от original. Во многих других языках массив — это ссылка, и такое присваивание сделало бы original и copy двумя именами одних и тех же данных, так что изменение через любое из них было бы видно через другое. В Swift это не так: Array — тип-значение, как Int или String. На практике это значит, что можно спокойно передавать массив в функцию, не боясь, что она незаметно испортит исходные данные вызывающего кода.
Частые ошибки
- Обращение по индексу, которого нет:
array[array.count]вместоarray[array.count - 1]для последнего элемента — такая опечатка на единицу аварийно завершает программу. - Ожидание, что
firstиlastвернут сам элемент, а неOptional— компилятор потребует развернуть значение, прежде чем его использовать. - Работа со срезом так, будто его индексы начинаются с нуля —
slice[0]падает, если исходный массив начинался не с нулевого индекса среза. - Попытка изменить массив, объявленный через
let—appendи подобные методы недоступны, пока массив не переобъявлен черезvar. - Забыть, что присваивание копирует массив, и удивляться, что изменения в одной переменной не видны в другой.
Резюме
[Element]— упорядоченная коллекция значений одного типа, полная форма записи —Array<Element>.- Индексация начинается с нуля; выход за границы — аварийное завершение программы, а не
nil. - Безопасный доступ:
indices.contains,first/last(возвращаютOptional),isEmpty,count. - Изменение:
append,+=,insert(at:),remove(at:),removeLast,removeAll— только уvar. - Срез через диапазон индексов — это
ArraySlice, его индексы наследуются от исходного массива. - Массив — тип-значение: присваивание и передача в функцию копируют его, а не разделяют одни данные на двоих.
Проверьте себя
3 вопроса
Сумма элементов массива
Напишите функцию sumArray, которая принимает массив целых чисел [Int] и возвращает сумму всех его элементов.
Пройдите по массиву в цикле for-in, накапливая сумму в отдельной переменной. Если массив пустой, функция должна вернуть 0.
Отбор чётных чисел
Напишите функцию keepEven, которая принимает массив целых чисел [Int] и возвращает новый массив, содержащий только чётные числа из исходного, в том же порядке.
Заведите пустой массив-результат, пройдите по исходному массиву циклом for-in и добавляйте в результат через append только те числа, которые делятся на 2 без остатка. Если чётных чисел нет или исходный массив пуст, верните пустой массив.
Сдвиг массива влево
Напишите функцию shiftLeft, которая принимает массив целых чисел [Int] и возвращает новый массив, в котором первый элемент перемещён в конец, а остальные сдвинуты на одну позицию влево. Например, из [1, 2, 3] должно получиться [2, 3, 1].
Если массив пустой, верните его без изменений. Исходный массив, переданный в функцию, менять нельзя — работайте с его копией.