Перейти к содержимому
Теория · Массивы · урок 7 из 8

Копии массивов

Вернёмся к ряду шкафчиков. Что происходит, когда в программе стоит var b = a? Представьте, что возле первого ряда строят второй и раскладывают по нему такие же вещи. Дальше ряды живут каждый сам по себе: если положить что-то во второй ряд, первый не изменится.

Посмотрим, как это выглядит в коде:

SWIFT
let a = [1, 2, 3]
var b = a
b.append(4)
print(a)  // [1, 2, 3]
print(b)  // [1, 2, 3, 4]

Давайте разберём программу по строкам:

  1. let a = [1, 2, 3] строит первый ряд.
  2. var b = a строит второй ряд с теми же элементами. a при этом ничего не теряет: присваивание копирует элементы, а не забирает их.
  3. b.append(4) пристраивает шкафчик только ко второму ряду.
  4. a печатается как прежде, [1, 2, 3], а b — с новым элементом.

Обратите внимание: a объявлена через let, а b — через var. У копии своё объявление, поэтому b можно менять, хотя a — константа.

Массив — значимый тип

Тип, у которого присваивание и передача в функцию создают независимую копию, называют значимым типом. Массивы в Swift — значимый тип, как Int и String. После var y = x никто не ждёт, что новое значение в y изменит число в x. С массивами всё так же.

Нажмите «Следующий шаг» и сравнивайте a с b, а потом с массивами внутри функции: меняется только тот массив, к которому применили append.

ВживуюКаждый массив — отдельная копияшаг 1 из 12 · рамок: 1

Константа a — первый ряд из трёх элементов.

Следите за массивом a: его значение не меняется ни на одном шаге

Функция тоже получает свою копию массива. В примере withBonus получает a в параметр scores и меняет не его, а копию result, потому что параметр — константа. После вызова a прежняя.

На собеседовании часто спрашивают: что станет с первым массивом, если изменить второй? В Swift — ничего: массив — значимый тип. Вопрос задают потому, что во многих других языках, например в JavaScript и Python, оба имени вели бы к одному и тому же массиву.

Когда Swift копирует элементы

Копировать большой массив при каждом присваивании было бы долго, поэтому Swift делает это не сразу. Пока ни один из массивов не меняли, они пользуются одними и теми же элементами. Перед первым изменением одного из них Swift копирует для него элементы. Такой приём называют копированием при записи.

Здесь картинка с двумя рядами неточна. Второй ряд строят не в момент var b = a, а когда в одном из рядов впервые что-то меняют. Но по работе программы этого не заметить: массивы всегда ведут себя так, будто копию сделали сразу.

Частая ошибка

Функция пытается добавить элемент в массив, который ей передали:

SWIFT
func addScore(_ list: [Int]) {
    list.append(5) // ✗
}
TEXT
error: cannot use mutating member on immutable value: 'list' is a 'let' constant

Программа не компилируется: параметр — константа с копией массива. Но даже будь он переменной, изменилась бы только копия внутри функции. Чтобы функция изменила массив вызывающего кода, параметр объявляют как inout, а при вызове ставят &:

SWIFT
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 копирует элементы перед первым изменением, но программа ведёт себя так, будто копию сделали сразу.