Web

Копируются ли массивы в PHP как значения или как ссылки на новые переменные при передаче в функции

1) Когда массив передается в качестве аргумента методу или функции, он передается по ссылке или по значению?

2) Когда массив присваивается переменной, является ли новая переменная ссылкой на исходный массив, или это новая копия?

А если сделать следующее:

$a = array(1,2,3);

$b = $a;

 Является ли $b ссылкой на $a?

 

Ответ 1

Что касается второй части вашего вопроса обратитесь к странице руководства, посвященной массивам, где говорится (цитирую):

Присвоение массива всегда включает копирование значения. Для копирования массива по ссылке используйте оператор ссылки.

 А в приведенном примере:

<?php

$arr1 = array(2, 3);

$arr2 = $arr1;

$arr2[] = 4; // $arr2 изменяется,

             // $arr1 все еще массив(2, 3)

$arr3 = &$arr1;

$arr3[] = 4; // теперь $arr1 и $arr3 одинаковы

?>

 Что касается первой части, то лучший способ определить — попробовать ;-)

Рассмотрим этот пример кода:

function my_func($a) {

    $a[] = 30;

}

$arr = array(10, 20);

my_func($arr);

var_dump($arr);

 Это даст следующий результат:

array

  0 => int 10

  1 => int 20

 Это указывает на то, что функция не модифицировала «внешний» массив, который был передан в качестве параметра: он передан как копия, а не как ссылка. Если вы хотите, чтобы он передавался по ссылке, вам придется модифицировать функцию таким образом:

function my_func(& $a) {

    $a[] = 30;

}

 И на выходе получится:

array

  0 => int 10

  1 => int 20

  2 => int 30

 поскольку на этот раз массив был передан «по ссылке». Не поленитесь прочитать раздел руководства «Объяснение ссылок», он должен ответить на некоторые из ваших вопросов.

 

 Ответ 2

Что касается вашего первого вопроса: массив передается по ссылке, пока он не изменен в методе/функции, которую вы вызываете. Если вы пытаетесь изменить массив внутри метода/функции, сначала создается его копия, а затем изменяется только копия. Это создает впечатление, что массив передается по значению, хотя на самом деле это не так.

Например, в первом случае, несмотря на то, что вы не определяете свою функцию как принимающую $my_array по ссылке (используя символ & в определении параметра), он все равно передается по ссылке (т. е. вы не тратите память на ненужную копию).

function handle_array($my_array) {  

    // ... читать, но не изменять $my_array

    print_r($my_array);

    // ... $my_array фактически передается по ссылке, поскольку копия не создается

}

Однако если вы изменяете массив, сначала создается его копия (что использует больше памяти, но не затрагивает исходный массив).

function handle_array($my_array) {

    // ... изменяем $my_array

    $my_array[] = "New value";

    // ... $my_array эффективно передается по значению, так как требует локальной копии

}

К сведению, это известно как «ленивое копирование» или «копирование при записи».



Ответ 3

a) метод/функция только читает аргумент массива => неявная (внутренняя) ссылка

b) метод/функция изменяет аргумент массива => значение

c) аргумент массива метода/функции явно помечен как ссылка (амперсандом) => явная (пользовательская) ссылка

Или так:

- неамперсандный массив param: передается по ссылке; операции записи изменяют новую копию массива, копия, которая создается при первой записи;

- амперсандный массив param: передается по ссылке; операции записи изменяют исходный массив.

Помните: PHP выполняет копирование значения в тот момент, когда вы пишете в неамперсандный массив param. Вот что означает копирование при записи. Я бы с радостью показал вам исходник этого поведения на C, но там все очень страшно. Лучше используйте xdebug_debug_zval().

Ответ

Зависит от ситуации.

Длинная версия

Всякий раз, когда люди говорят о ссылках (или указателях, если на то пошло), они обычно попадают в «магию» (только посмотрите на эту тему!).

Как вы увидите, вся эта штука с «по значению/по ссылке» очень сильно связана с тем, что именно вы делаете с этим массивом в области видимости вашего метода/функции: читаете его или изменяете.

Что говорится в руководстве PHP?

По умолчанию аргументы функции передаются по значению (так что если значение аргумента внутри функции изменено, оно не будет изменено вне функции). Чтобы функция могла изменять свои аргументы, они должны передаваться по ссылке.

Чтобы аргумент функции всегда передавался по ссылке, добавьте амперсанд (&) к имени аргумента в определении функции. Насколько я могу судить, когда серьезные программисты говорят о ссылках, они обычно говорят об изменении значения этой ссылки. И это именно то, о чем говорится в руководстве: если вы хотите ИЗМЕНИТЬ значение в функции, считайте, что PHP делает «передачу по значению».

Однако есть еще один случай, о котором они не упоминают: что если я ничего не меняю, только читаю?

Что если вы передаете массив методу, который явно не помечает ссылку, и мы не изменяем этот массив в области видимости функции? Например:

<?php

function readAndDoStuffWithAnArray($array)  {

    return $array[0] + $array[1] + $array[2];

}

$x = array(1, 2, 3);

echo readAndDoStuffWithAnArray($x);

 Что на самом деле делает PHP? («с точки зрения памяти»)

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

<?php

// заполнение массива с 10000 элементами int 1

// допустим, это займет 3 мб вашей оперативной памяти

$x = array_fill(0, 10000, 1); 

// Передача по значению, верно? ПРАВИЛЬНО?

function readArray($arr) { // <-- здесь создается новый символ (переменная)

    echo count($arr); // просто прочитаем массив

}

readArray($x);

 Теперь, если бы это действительно было передачей по значению, мы бы потеряли 3 Мб ОЗУ, потому что есть две копии этого массива, верно?

Неверно. До тех пор, пока мы не изменим переменную $arr, это ссылка, с точки зрения памяти. Вы просто не видите ее. Вот почему PHP упоминает пользовательские ссылки, когда говорит о &$someVar, чтобы отличить внутренние и явные переменные (с амперсандом).

Факты

Итак, когда массив передается в качестве аргумента методу или функции, передается ли он по ссылке?

 Я знаю три случая:

a) метод/функция только считывает аргумент массива;

b) метод/функция изменяет аргумент массива;

c) аргумент массива метода/функции явно помечен как ссылка (амперсандом).

Во-первых, давайте посмотрим, сколько памяти на самом деле занимает этот массив:

<?php

$start_memory = memory_get_usage();

$x = array_fill(0, 10000, 1);

echo memory_get_usage() - $start_memory; // 1331840

 a) метод/функция читает только аргумент массива

Теперь сделаем функцию, которая читает только указанный массив в качестве аргумента, и посмотрим, сколько памяти занимает логика чтения:

<?php

function printUsedMemory($arr)  {

    $start_memory = memory_get_usage();

    count($arr);       // чтение

    $x = $arr[0];      // чтение (+второстепенная операция)

    $arr[0] - $arr[1]; // чтение

    echo memory_get_usage() - $start_memory; // посмотрим, сколько памяти используется во время чтения

}

$x = array_fill(0, 10000, 1); // 1331840 байт

printUsedMemory($x);

У меня 80. Это та часть, которую руководство PHP опускает. Если бы параметр $arr действительно передавался по значению, вы бы увидели что-то похожее на 1331840 байт. Кажется, что $arr ведет себя как ссылка, не так ли? Потому что это и есть ссылка — внутренняя.

б) метод/функция изменяет аргумент массива

Теперь давайте запишем в этот параметр, вместо того чтобы читать из него:

<?php

function printUsedMemory($arr) {

    $start_memory = memory_get_usage();

    $arr[0] = 1; // WRITE!

    echo memory_get_usage() - $start_memory; // посмотрим, сколько памяти используется во время чтения

}

$x = array_fill(0, 10000, 1);

printUsedMemory($x);

 Опять же, для меня это довольно близко к 1331840. Так что в этом случае массив действительно копируется в $arr.

c) аргумент массива метода/функции явно помечен как ссылка (амперсандом)

Теперь посмотрим, сколько памяти занимает операция записи в явную ссылку обратите внимание на амперсанд в сигнатуре функции:

<?php

function printUsedMemory(&$arr)  {

    // явный, пользовательский, передаваемый по ссылке

    $start_memory = memory_get_usage();

    $arr[0] = 1; // WRITE!

    echo memory_get_usage() - $start_memory; // посмотрим, сколько памяти используется во время чтения

}

$x = array_fill(0, 10000, 1);

printUsedMemory($x);

 Таким образом, это съедает примерно столько же памяти, сколько чтение из параметра без амперсанда.

 

Ответ 4

Существуют следующие варианты:

  1. Примитивы передаются по значению. В отличие от Java, строка является примитивом в PHP.

  2. Массивы примитивов передаются по значению.

  3. Объекты передаются по ссылке.

  4. Массивы объектов передаются по значению, но каждый объект передается по ссылке.

<?php

$obj=new stdClass();

$obj->field='world';

$original=array($obj);

function example($hello) {

    $hello[0]->field='mundo'; // изменение будет применено в $original

    $hello[1]=new stdClass();// изменение не будет применено в $original

   $

}

example($original);

var_dump($original);

// array(1) { [0]=> object(stdClass)#1 (1) { ["field"]=> string(5) "mundo" } }

Примечание: в качестве оптимизации каждое значение передается как ссылка до тех пор, пока оно не будет изменено внутри функции. Если значение изменено, а оно было передано по ссылке, то оно копируется, а копия модифицируется.

 

Ответ 5

Когда массив передается методу или функции в PHP, он передается по значению, если вы явно не передаете его по ссылке, как, например, в этом случае:

function test(&$array) {

    $array['new'] = 'hey';

}

 

$a = $array(1,2,3);

// prints [0=>1,1=>2,2=>3]

var_dump($a);

test($a);

// prints [0=>1,1=>2,2=>3,'new'=>'hey']

var_dump($a);

Во втором вопросе $b — это не ссылка на $a, а копия $a. Как и в первом примере, вы можете сослаться на $a, сделав следующее:

$a = array(1,2,3);

$b = &$a;

// prints [0=>1,1=>2,2=>3]

var_dump($b);

$b['new'] = 'hey';

// prints [0=>1,1=>2,2=>3,'new'=>'hey']

var_dump($a);



Ответ 6

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

<?php

$foo = array( array(1,2,3), 22, 33);

function hello($fooarg) {

  $fooarg[0][0] = 99;

}

 

function world(&$fooarg) {

  $fooarg[0][0] = 66;

}

hello($foo);

var_dump($foo); // (исходный массив не изменен) массив передан по значению

world($foo);

var_dump($foo); // (исходный массив изменен) массив передан по ссылке

В результате:

array(3) {

  [0] => array(3) {

    [0] => int(1)

    [1] => int(2)

    [2] => int(3)

  }

  [1] => int(22)

  [2] => int(33)

}

array(3) {

  [0] => array(3) {

    [0] => int(66)

    [1] => int(2)

    [2] => int(3)

  }

  [1] => int(22)

  [2] => int(33)

}



Ответ 7

Попробуйте этот код:

$date = new DateTime();

$arr = ['date' => $date];

echo $date->format('Ymd') . '<br>';

mytest($arr);

echo $date->format('Ymd') . '<br>';

function mytest($params = []) {

    if (isset($params['date'])) {

        $params['date']->add(new DateInterval('P1D'));

Обратите внимание, что нет амперсанда для параметра $params, и все равно он изменяет значение $arr['date']. Это не совсем соответствует всем другим объяснениям здесь и тому, что я думал до сих пор. Если я клонирую объект $params['date'], вторая выводимая дата остается неизменной. Если я просто установлю ее в строку, это также не повлияет на вывод.

 

Схожие статьи

Сравнение VPN-сервисов: выбираем лучший по нескольким критериям
Web

Сравнение VPN-сервисов: выбираем лучший по нескольким критериям

Валидный e-mail: что это, проверка почты на валидность через валидатор
Web

Валидный e-mail: что это, проверка почты на валидность через валидатор

Web

Получение информации о дорожке из аудиопотока с помощью PHP

Web

Сохранение изображения из URL-адреса в PHP

×