Копии массивов
Вернёмся к ряду шкафчиков. Что происходит, когда в программе стоит var b = a? Представьте, что возле первого ряда строят второй и раскладывают по нему такие же вещи. Дальше ряды живут каждый сам по себе: если положить что-то во второй ряд, первый не изменится.
Посмотрим, как это выглядит в коде:
let a = [1, 2, 3]
var b = a
b.append(4)
print(a) // [1, 2, 3]
print(b) // [1, 2, 3, 4]Давайте разберём программу по строкам:
let a = [1, 2, 3]строит первый ряд.var b = aстроит второй ряд с теми же элементами.aпри этом ничего не теряет: присваивание копирует элементы, а не забирает их.b.append(4)пристраивает шкафчик только ко второму ряду.aпечатается как прежде,[1, 2, 3], аb— с новым элементом.
Обратите внимание: a объявлена через let, а b — через var. У копии своё объявление, поэтому b можно менять, хотя a — константа.
Массив — значимый тип
Тип, у которого присваивание и передача в функцию создают независимую копию, называют значимым типом. Массивы в Swift — значимый тип, как Int и String. После var y = x никто не ждёт, что новое значение в y изменит число в x. С массивами всё так же.
Нажмите «Следующий шаг» и сравнивайте a с b, а потом с массивами внутри функции: меняется только тот массив, к которому применили append.
1func withBonus(_ scores: [Int]) -> [Int] {2 var result = scores3 result.append(100)4 return result5}6 7let a = [1, 2, 3]8var b = a9b.append(4)10print(a)11print(b)12let c = withBonus(a)13print(a)14print(c)
Константа a — первый ряд из трёх элементов.
Функция тоже получает свою копию массива. В примере withBonus получает a в параметр scores и меняет не его, а копию result, потому что параметр — константа. После вызова a прежняя.
На собеседовании часто спрашивают: что станет с первым массивом, если изменить второй? В Swift — ничего: массив — значимый тип. Вопрос задают потому, что во многих других языках, например в JavaScript и Python, оба имени вели бы к одному и тому же массиву.
Когда Swift копирует элементы
Копировать большой массив при каждом присваивании было бы долго, поэтому Swift делает это не сразу. Пока ни один из массивов не меняли, они пользуются одними и теми же элементами. Перед первым изменением одного из них Swift копирует для него элементы. Такой приём называют копированием при записи.
Здесь картинка с двумя рядами неточна. Второй ряд строят не в момент var b = a, а когда в одном из рядов впервые что-то меняют. Но по работе программы этого не заметить: массивы всегда ведут себя так, будто копию сделали сразу.
Частая ошибка
Функция пытается добавить элемент в массив, который ей передали:
func addScore(_ list: [Int]) {
list.append(5) // ✗
}error: cannot use mutating member on immutable value: 'list' is a 'let' constantПрограмма не компилируется: параметр — константа с копией массива. Но даже будь он переменной, изменилась бы только копия внутри функции. Чтобы функция изменила массив вызывающего кода, параметр объявляют как inout, а при вызове ставят &:
func addScore(_ list: inout [Int]) {
list.append(5)
}
addScore(&b)
print(b) // [1, 2, 3, 4, 5]Главное
- Массив — значимый тип:
var b = aсоздаёт независимую копию, и измененияbне видны вa. - Присваивание не опустошает исходный массив, а у копии своё объявление: массив из
letможно скопировать вvarи менять. - Функция получает копию массива и изменить массив вызывающего кода может только через
inoutи&. - Swift копирует элементы перед первым изменением, но программа ведёт себя так, будто копию сделали сразу.