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

Копия массива при присваивании

Как вы уже знаете, $b = $a копирует число или строку: позднее изменение $a не трогает $b. А что, если в $a лежит целый массив — гардероб со всеми крючками и вещами?

Представьте, что вы просите гардеробщика: «Сделайте мне такой же». И рядом появляется второй гардероб — с теми же крючками и теми же вещами на них. Теперь гардеробов два: повесите пальто во второй, и в первом ничего не изменится.

В PHP присваивание массива работает именно так: $b = $a даёт второй, независимый массив. Нажмите «Следующий шаг» и следите за $a и $b в рамке {main}: после присваивания каждая запись меняет только один из них.

ВживуюДве переменные — два массивашаг 1 из 6 · рамок: 1

В $a лежит массив из двух элементов.

Следите за $a и $b в рамке {main}

Разберём, что показал пример:

  1. $b = $a создаёт второй массив с теми же элементами [1, 2].
  2. $b[] = 3 добавляет элемент только в $b: count($a) остаётся 2, а count($b) становится 3.
  3. $a[0] = 9 меняет только $a, поэтому $b[0] по-прежнему 1.
  4. Программа печатает 2 3 и 9 1.

На собеседовании часто спрашивают, копируется ли массив при присваивании, особенно тех, кто знает Python или JavaScript. Там b = a даёт второе имя тому же списку, и изменение через b видно через a. В PHP массив при присваивании копируется, и переменные живут отдельно.

Вложенные массивы тоже копируются

Копируется весь гардероб вместе с сумками на крючках: вложенные массивы в копии тоже становятся отдельными.

PHP
<?php

$menu = ['drinks' => ['tea', 'coffee']];
$copy = $menu;
$copy['drinks'][] = 'juice';
echo count($menu['drinks']), ' ', count($copy['drinks']), "\n"; // 2 3
echo implode(' ', $menu['drinks']), "\n"; // tea coffee

Запись $copy['drinks'][] = 'juice' меняет только список внутри $copy. В $menu по-прежнему два напитка, tea и coffee.

Когда PHP делает копию

Копировать большой массив на каждом $b = $a было бы долго, поэтому PHP откладывает эту работу. Сразу после присваивания обе переменные смотрят на одни и те же данные, а настоящая копия появляется при первой записи в одну из них. Такой приём называют копированием при записи.

Для вашей программы разницы нет: массивы ведут себя как два независимых. Здесь картинка с гардеробом перестаёт работать: настоящий второй гардероб строят сразу, а PHP строит его, только когда кто-то вешает или снимает вещь.

Массив в функции

То же происходит, когда массив передают в функцию: параметр получает свою копию массива. Если функция добавит в параметр элемент, массив, переданный при вызове, останется прежним. Благодаря копированию при записи передать в функцию большой массив недорого.

На собеседовании спрашивают, сколько стоит передать в функцию массив из миллиона элементов. Ответ: почти ничего. Пока функция только читает массив, копии нет: PHP скопирует данные, лишь когда функция начнёт в них писать.

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

Возраст всех пользователей увеличивают в цикле foreach, меняя переменную цикла:

PHP
$users = [
    ['name' => 'Anna', 'age' => 31],
    ['name' => 'Boris', 'age' => 25],
];
foreach ($users as $user) {
    $user['age'] = $user['age'] + 1; // ✗
}
echo $users[0]['age'], ' ', $users[1]['age'], "\n"; // 31 25

Программа работает без сообщений, но возраст не меняется: печатается 31 25. Это вы видели в теме о циклах: foreach кладёт в $user копию элемента, а элемент здесь — целая карточка. Запись меняет копию, и после витка она пропадает.

Пишите в сам массив по ключу: foreach ($users as $i => $user) { $users[$i]['age'] = $user['age'] + 1; }. После такого цикла печатается 32 26.

Главное

  • $b = $a даёт второй, независимый массив: запись в один не видна в другом.
  • Вложенные массивы копируются вместе с внешним, а при передаче в функцию параметр тоже получает копию.
  • PHP копирует данные только при первой записи — это копирование при записи, и для программы массивы всё равно независимы.
  • Переменная цикла foreach — копия элемента, поэтому массив меняют по ключу: $users[$i]['age'] = ….