Чи копіюються масиви в PHP як значення або як посилання на нові змінні та коли передаються функції?


259

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

2) При призначенні масиву змінній чи нова змінна є посиланням на вихідний масив чи це нова копія?
Що з цим робити:

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

Чи $bє посилання на $a?


Також дивіться When-does-foreach-copy
nawfal

3
@MarlonJerezIsla: схоже, що масив клонований, лише якщо ви модифікуєте його всередині функції. Як і раніше з інших мов, це здається дивним.
користувач276648

Відповіді:


276

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

Призначення масиву завжди включає копіювання значення. Використовуйте контрольний оператор, щоб скопіювати масив за посиланням.

І наведений приклад:

<?php
$arr1 = array(2, 3);
$arr2 = $arr1;
$arr2[] = 4; // $arr2 is changed,
             // $arr1 is still array(2, 3)

$arr3 = &$arr1;
$arr3[] = 4; // now $arr1 and $arr3 are the same
?>


У першій частині найкращий спосіб переконатися - спробувати ;-)

Розглянемо цей приклад коду:

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

Оскільки цього разу масив передано "за посиланням".


Не соромтеся прочитати розділ Посилання Пояснення посібника: він повинен відповісти на деякі ваші запитання ;-)


як щодо чогось на зразок $ a = & $ this-> a. $ A тепер посилання на & this-> a?
Френк

1
Як ви користуєтесь &, так, так - дивіться php.net/manual/en/…
Паскаль МАРТИН

1
Свята корова, я не можу повірити, що це проблема, яку я мав ... якщо це буде урок, завжди читайте керівництво про право
Heavy_Bullets

2
Привіт Паскале, я виявив, що відповідь Коста Контоса є більш точною. Я роблю простий швидкий тест, щоб підтвердити його знахідку gist.github.com/anonymous/aaf845ae354578b74906 Чи можете ви також прокоментувати його висновок?
Чак Ян Ченг

1
Це проблема, з якою у мене виникли також: я вважав, що це щось дивне у вкладених масивах, але насправді це саме те, як працює призначення масивів у PHP.
Список Джеремі

120

Що стосується вашого першого запитання, масив передається за посиланням, БЕЗ ВИНАГИ він модифікується в межах методу / функції, яку ви викликаєте. Якщо ви намагаєтесь змінити масив у межах методу / функції, спочатку робиться його копія, а потім лише копія. Таким чином, здається, що масив передається за значенням, коли насправді це не так.

Наприклад, у цьому першому випадку, навіть якщо ви не визначаєте свою функцію приймати $ my_array за посиланням (використовуючи & символ у визначенні параметра), він все одно передається посиланням (тобто: ви не витрачаєте пам'ять з непотрібною копією).

function handle_array($my_array) {  

    // ... read from but do not modify $my_array
    print_r($my_array);

    // ... $my_array effectively passed by reference since no copy is made
}

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

function handle_array($my_array) {

    // ... modify $my_array
    $my_array[] = "New value";

    // ... $my_array effectively passed by value since requires local copy
}

FYI - це відоме як "ледача копія" або "копіювати на запис".


8
Це надзвичайно цікава інформація! Схоже, це правда; але я не міг знайти жодної офіційної документації, яка підтверджує цей факт. Нам також потрібно знати, які версії PHP підтримують цю ліниву концепцію копії. Хтось має більше інформації?
Маріо Авад

8
Оновлення, знайдено офіційну документацію, все ж потрібно знайти, яка версія PHP підтримує ледачу копію (в керівництві це називають "копіювати на написання"): php.net/manual/en/internals2.variables.intro.php
Mario Awad

7
Це суто рішення про реалізацію віртуальної машини PHP, а не частиною мови - воно фактично не видно програмісту. Копіювати при написанні, безумовно, рекомендується з міркувань продуктивності, але реалізація, яка копіює кожен масив, не має різниці з точки зору програміста, тому можна сказати, що мовна семантика визначає значення проходу.
Superfly

14
@Superfly, безумовно, має значення, коли я хочу знати, чи можу я передати свій 100 Мб масив через стек з десятків функцій, не втрачаючи пам'яті! Можливо, ви маєте рацію, що все-таки правильно називати семантику перехідною за значенням, але, залишаючи осторонь таких кайданів термінологію, згадана тут «деталізація реалізації», безумовно, має значення для програмістів PHP в реальному світі.
Марк Амері

3
До цього є ще одна химерність, яка робить усвідомлення копіювання під час написання ще важливішим, коли думаєте про продуктивність. Ви можете подумати, що передача масивів за посиланням економить пам’ять порівняно з передачею за значенням (якщо ви не знали про копіювання під час запису), але насправді це може мати протилежний ефект! Якщо масив потім передаються по значенню (за власним або 3 - ю коду партії), PHP , то є , щоб зробити повну копію або більше не може відстежувати кількість посилань! Більше тут: stackoverflow.com/questions/21974581/…
Дан Кінг,

80

TL; DR

а) метод / функція зчитує лише аргумент масиву => неявна (внутрішня) посилання
b) метод / функція змінює аргумент масиву => значення
в) аргумент масиву методу / функції явно позначений як посилання (з анкресом) => явна (користувальницька) посилання

Або це:
- параметр масиву без амперсанда : переданий посиланням; операції запису змінюють нову копію масиву, копія якої створюється при першому записі;
- параметр масиву ampersand : передається посиланням; операції запису змінюють вихідний масив.

Пам'ятайте - PHP робить копіює значення, коли ви пишете в параметр масиву без амперсанда. Ось що copy-on-writeозначає. Я хотів би показати вам джерело С такої поведінки, але там страшно. Краще використовувати xdebug_debug_zval () .

Паскаль МАРТИН мав рацію. Коста Контос був ще більше.

Відповідь

Це залежить.

Довга версія

Я думаю, що я це записую для себе. Я повинен мати блог чи щось таке ...

Кожного разу, коли люди говорять про посилання (або вказівники, з цього приводу), вони зазвичай опиняються в логомачі (просто подивіться на цю тему !).
PHP, будучи поважною мовою, подумав, що мені слід додати плутанину (хоча це резюме вищезазначених відповідей). Тому що, хоча двоє людей можуть мати рацію одночасно, вам краще просто розбити голову разом в одну відповідь.

По-перше, ви повинні знати, що ви не педант, якщо не відповідаєте чорно-білим чином . Речі складніші, ніж "так / ні".

Як ви побачите, вся річ за значенням / за посиланням дуже пов'язана з тим, що саме ви робите з цим масивом у вашому методі / функції: читання чи модифікація?

Що говорить PHP? (він же "змін")

Керівництво каже , що це (курсив мій):

За замовчуванням аргументи функції передаються за значенням (так що якщо значення аргументу в межах функції буде змінено , воно не буде змінено поза функцією). Щоб функція могла змінювати свої аргументи, вони повинні бути передані посиланням .

Щоб аргумент функції, яка завжди передається посиланням, додайте амперсанд (&) до імені аргументу у визначенні функції

Наскільки я можу сказати, коли великі, серйозні, чесні до Бога програмісти говорять про посилання, вони зазвичай говорять про зміну значення цього посилання . І це саме те , що ручні переговори про: hey, if you want to CHANGE the value in a function, consider that PHP's doing "pass-by-value".

Однак є й інший випадок, який вони не згадують: що робити, якщо я нічого не зміню - просто прочитаю?
Що робити, якщо ви передаєте масив методу, який не позначає явно посилання, і ми не змінюємо цей масив в області функцій? Наприклад:

<?php
function readAndDoStuffWithAnArray($array) 
{
    return $array[0] + $array[1] + $array[2];
}

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

echo readAndDoStuffWithAnArray($x);

Читайте далі, мій попутник.

Що насправді робить PHP? (він же "пам'ять мудрий")

Ті ж великі і серйозні програмісти, коли вони стають ще серйознішими, вони говорять про "оптимізацію пам'яті" щодо посилань. Так само і PHP. Тому PHP is a dynamic, loosely typed language, that uses copy-on-write and reference countingщо, ось чому .

Не було б ідеально передавати ОГРОМНІ масиви різним функціям, а PHP робити їх копії (саме це робить "pass-by-value").

<?php

// filling an array with 10000 elements of int 1
// let's say it grabs 3 mb from your RAM
$x = array_fill(0, 10000, 1); 

// pass by value, right? RIGHT?
function readArray($arr) { // <-- a new symbol (variable) gets created here
    echo count($arr); // let's just read the array
}

readArray($x);

Ну, якщо б це насправді було пропущеним значенням, у нас би залишилось 3 Мб + ОЗУ, тому що є дві копії цього масиву, правда?

Неправильно. Поки ми не змінюємо $arrзмінну, це посилання на пам'ять . Ви просто цього не бачите. Ось чому PHP згадує користувальницькі посилання під час розмови &$someVar, щоб розрізняти внутрішні та явні (з амперсандом).

Факти

Так, when an array is passed as an argument to a method or function is it passed by reference?

Я придумав три (так, три) випадки:
а) метод / функція зчитує лише аргумент масиву
б) метод / функція змінює аргумент масиву
в) аргумент масиву методу / функції явно позначається як посилання (з ampersand)


По-перше, давайте подивимося, скільки пам’яті цей масив фактично їсть (запустіть тут ):

<?php
$start_memory = memory_get_usage();
$x = array_fill(0, 10000, 1);
echo memory_get_usage() - $start_memory; // 1331840

Та багато байтів. Чудово.

а) метод / функція зчитує лише аргумент масиву

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

<?php

function printUsedMemory($arr) 
{
    $start_memory = memory_get_usage();

    count($arr);       // read
    $x = $arr[0];      // read (+ minor assignment)
    $arr[0] - $arr[1]; // read

    echo memory_get_usage() - $start_memory; // let's see the memory used whilst reading
}

$x = array_fill(0, 10000, 1); // this is 1331840 bytes
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; // let's see the memory used whilst reading
}

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

Знову ж , подивитися на себе , але, для мене, це досить близько до 1331840. Так що в цьому випадку масив буде фактично скопійований в $arr.

в) аргумент методу / функції явно позначений як посилання (з ампресом)

Тепер давайте подивимося, скільки пам’яті займає операція запису до явного посилання (запустіть тут ) - відмітьте амперсанд у підписі функції:

<?php

function printUsedMemory(&$arr) // <----- explicit, user-land, pass-by-reference
{
    $start_memory = memory_get_usage();

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

    echo memory_get_usage() - $start_memory; // let's see the memory used whilst reading
}

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

Моя обставина, ви отримуєте 200 макс! Таким чином, це з'їдає приблизно стільки ж пам’яті, скільки читання з параметри, що не належить до амперсанда .


Врятувало мене пару годин у налагодженні витоку пам’яті!
Раген Дазс

2
Коста Контос: Це настільки важливе питання, що ви повинні відзначити це як прийняту відповідь. Це сказав @nevvermind: Чудовий нарис, але будь ласка, включіть верхній розділ TL; DR.
AVIDeveloper

1
@nevvermind: Я не є абревіатурою акроніма, головна різниця полягає в тому, що Висновки з’являються зазвичай в кінці статті, тоді як TL; DR з'являється як перший рядок для тих, кому просто потрібно короткий відповідь, а не переглядати тривалий аналіз . Ваше дослідження добре, і це не є критикою, лише мої $ 00,02.
AVIDeveloper

1
Ти маєш рацію. Я ставлю висновки вгорі. Але я все ж хотів би, щоб люди перестали лінуватися читати все, перш ніж дійти до будь-якого висновку . Прокручування нам занадто легко, щоб заважати змінювати порядок речей.
неввермінд

1
Я думаю, що PHP стала ефективнішою через роки, тому що ваші приклади кодексу дають набагато менші цифри :)
drzaus

14

За замовчуванням

  1. Примітиви передаються за значенням. Навряд чи для Java, рядок примітивний у PHP
  2. Масиви примітивів передаються за значенням
  3. Об'єкти передаються шляхом посилання
  4. Масиви об'єктів передаються за значенням (масив), але кожен об'єкт передається за посиланням.

    <?php
    $obj=new stdClass();
    $obj->field='world';
    
    $original=array($obj);
    
    
    function example($hello) {
        $hello[0]->field='mundo'; // change will be applied in $original
        $hello[1]=new stdClass(); // change will not be applied in $original
        $
    }
    
    example($original);
    
    var_dump($original);
    // array(1) { [0]=> object(stdClass)#1 (1) { ["field"]=> string(5) "mundo" } } 

Примітка. В якості оптимізації кожне окреме значення передається як посилання до його зміни всередині функції. Якщо це змінено і значення передано за посиланням, воно копіюється і копія модифікується.


4
Ця відповідь має бути +1 у верхній частині. Він містить незрозумілу gotcha, яку інші відповіді не згадують: "4 - масиви об'єктів передаються за значенням (масив), але кожен об'єкт передається за посиланням". Я чухав голову через це!
серпень

@magallanes great слід оцінити першим і для мене, ви поясніть мені проблему масиву об'єктів, які у мене були. Чи є спосіб змінити об'єкт у масиві лише в одній із двох змінних масиву (оригінал та копія)?
fede72bari

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);

1

Ця тема трохи старша, але тут я щойно натрапив:

Спробуйте цей код:

$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'));
    }
}

http://codepad.viper-7.com/gwPYMw

Зауважте, що для параметра $ params немає амп, і він все ще змінює значення $ arr ['date']. Це насправді не відповідає всім іншим поясненням тут і тим, що я думав дотепер.

Якщо я клоную об’єкт $ params ['date'], друга дата, що виводиться, залишається такою ж. Якщо я просто встановив його на рядок, він також не впливає на вихід.


3
Масив копіюється, але це не глибока копія. Це означає, що примітивні значення, такі як числа та рядки, копіюються в $ param, але для об'єктів посилання копіюється замість клонованого об'єкта. $ arr містить посилання на $ date, а також скопійований масив $ params. Отже, коли ви викликаєте функцію на $ params ['date'], яка змінює її значення, ви також змінюєте $ arr ['date'] та $ date. Коли ви встановлюєте $ params ['date'] на рядок, ви просто замінюєте посилання $ params на $ date чимось іншим.
ejegg

1

Для розширення однієї з відповідей також підмножини багатовимірних масивів передаються за значенням, якщо не передаються чітко за посиланням.

<?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); // (original array not modified) array passed-by-value

world($foo);
var_dump($foo); // (original array modified) array passed-by-reference

Результат:

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)
}

0

У PHP масиви передаються функціям за значенням за замовчуванням, якщо ви явно не передаєте їх за посиланням, як показує наступний фрагмент:

$foo = array(11, 22, 33);

function hello($fooarg) {
  $fooarg[0] = 99;
}

function world(&$fooarg) {
  $fooarg[0] = 66;
}

hello($foo);
var_dump($foo); // (original array not modified) array passed-by-value

world($foo);
var_dump($foo); // (original array modified) array passed-by-reference

Ось вихід:

array(3) {
  [0]=>
  int(11)
  [1]=>
  int(22)
  [2]=>
  int(33)
}
array(3) {
  [0]=>
  int(66)
  [1]=>
  int(22)
  [2]=>
  int(33)
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.