PHP'deki diziler değer olarak veya yeni değişkenlere referans olarak ve işlevlere aktarıldığında kopyalanıyor mu?


259

1) Bir dizi, bir yönteme veya işleve bağımsız değişken olarak iletildiğinde, başvuru veya değere göre mi iletilir?

2) Bir değişkene dizi atarken, yeni değişken orijinal diziye bir başvuru mu, yoksa yeni bir kopya mı?
Bunu yapmaya ne dersiniz:

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

$bbir referans $a?



3
@MarlonJerezIsla: Dizi yalnızca işlev içinde değiştirirseniz klonlanmış gibi görünür. Hala diğer dillerden geliyor, tuhaf görünüyor.
user276648

Yanıtlar:


276

Sorunuzun ikinci kısmı için, bkz kılavuzun dizisi sayfasını devletler (alıntı) :

Dizi ataması her zaman değer kopyalamayı içerir. Bir diziyi başvuru ile kopyalamak için başvuru işlecini kullanın.

Ve verilen örnek:

<?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
?>


İlk bölümde, emin olmanın en iyi yolu denemektir ;-)

Şu kod örneğini düşünün:

function my_func($a) {
    $a[] = 30;
}

$arr = array(10, 20);
my_func($arr);
var_dump($arr);

Bu çıktıyı verecektir:

array
  0 => int 10
  1 => int 20

Bu, işlevin parametre olarak iletilen "dış" diziyi değiştirmediğini gösterir: bir kopya olarak değil referans olarak iletilir.

Başvuru ile geçilmesini istiyorsanız, işlevi şu şekilde değiştirmeniz gerekir:

function my_func(& $a) {
    $a[] = 30;
}

Ve çıktı:

array
  0 => int 10
  1 => int 20
  2 => int 30

Bu kez, dizi "referans olarak" geçti.


Kılavuzun Referanslar Açıklaması bölümünü okumaktan çekinmeyin : bazı sorularınıza cevap vermelidir ;-)


$ a = & $ this-> a gibi bir şeye ne dersiniz? $ A şimdi & this-> a için bir referans mı?
Frank

1
Kullandığınız gibi &, evet, gereken - bkz php.net/manual/en/...
Pascal MARTIN

1
kutsal inek, ben bu sorun olduğunu inanamıyorum ... bu bir ders olmalı, her zaman offing kılavuzu okuyun
Heavy_Bullets

2
Merhaba Pascal, Kosta Kontos'un cevabının daha doğru olduğunu gördüm. Onun bulgu onaylamanın basit hızlı test yapmak gist.github.com/anonymous/aaf845ae354578b74906 sen onun çok bulgu hakkında ne diyorsunuz?
Cheok Yan Cheng

1
Bu da yaşadım sorun: iç içe diziler hakkında garip bir şey olduğunu düşündüm ama aslında sadece dizi ataması PHP çalışır.
Jeremy List

120

İlk sorunuzla ilgili olarak, dizi, aradığınız yöntem / işlev içinde değiştirilen UNLESS referansıyla iletilir. Yöntem / işlev içindeki diziyi değiştirmeye çalışırsanız, ilk önce bir kopyası oluşturulur ve daha sonra yalnızca kopya değiştirilir. Bu, dizinin gerçekte değilken değere göre geçirilmiş gibi görünmesini sağlar.

Örneğin, bu ilk durumda, $ my_array işlevini başvuru ile kabul etmek için işlevinizi tanımlamasanız bile (parametre tanımındaki & karakterini kullanarak), yine de başvuru ile iletilir (yani: belleği boşa harcamazsınız) gereksiz bir kopya ile).

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
}

Ancak diziyi değiştirirseniz, önce bir kopyası oluşturulur (bu da daha fazla bellek kullanır ancak orijinal dizinizi etkilenmeden bırakır).

function handle_array($my_array) {

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

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

FYI - Bu, "tembel kopyalama" veya "yazma üzerine kopyalama" olarak bilinir.


8
Bu çok ilginç bir bilgi parçası! Gerçek gibi görünüyor; ancak bu gerçeği destekleyen herhangi bir resmi belge bulamadım. Ayrıca bu tembel kopya kavramını hangi PHP sürümlerinin desteklediğini de bilmemiz gerekir. Daha fazla bilgiye sahip olan var mı?
Mario Awad

8
Güncelleme, bazı resmi belgeler buldu, hala PHP'nin hangi versiyonunun tembel kopyayı desteklediğini bulması gerekiyor (kılavuzda "yazarken kopyala" diyorlar): php.net/manual/en/internals2.variables.intro.php
Mario Awad

7
Bu tamamen PHP sanal makinesinin bir uygulama kararıdır ve dilin bir parçası değildir - aslında programcı tarafından görülemez. Performans nedenleriyle yazarken kopyalama kesinlikle önerilir, ancak her diziyi kopyalayan bir uygulamanın programcının bakış açısından hiçbir farkı yoktur, bu nedenle dil anlambiliminin pass-by-value belirttiğini söyleyebiliriz.
Superfly

14
@Superfly 100MB dizimi düzinelerce fonksiyon yığınından geçip geçemediğini bilmek istediğimde kesinlikle bir fark yaratıyor! Bununla birlikte, anlambilimsel geçiş değerini çağırmanın doğru olduğu doğru olabilir, ancak terminoloji üzerinde bu tür tartışmaları bir kenara bırakarak, burada belirtilen "uygulama detayı" gerçek dünyadaki PHP programcıları için kesinlikle önemlidir.
Mark Amery

3
Bunun üzerine başka bir tuhaflık daha var, bu da performans üzerine düşünürken yazma üzerine kopyalamanın farkında olmayı daha da önemli hale getiriyor. Dizilere referansla geçmenin, değere göre geçmeye kıyasla (bellekte kopyalama hakkında bilmiyorsanız) bellek tasarrufu sağladığını düşünebilirsiniz , ancak aslında tam tersi bir etkiye sahip olabilir ! Dizi daha sonra değere (kendi veya 3. taraf kodunuza göre) aktarılırsa, PHP tam bir kopya yapmak zorundadır veya artık referans sayısını izleyemez! Daha fazla burada: stackoverflow.com/questions/21974581/…
Dan King

80

TL; DR

a) yöntem / işlev yalnızca dizi bağımsız değişkenini okur => örtük (iç) başvuru
b) yöntem / işlev dizi bağımsız değişkenini değiştirir => değer
c) yöntem / işlev dizi bağımsız değişkeni açıkça bir başvuru işareti olarak işaretlenir (ve işareti ile) => açık (kullanıcı-arazi) referansı

Veya bu:
- ve işareti olmayan dizi parametresi : referans ile geçti; yazma işlemleri dizinin yeni bir kopyasını değiştirir, kopya ilk yazmada oluşturulur;
- ve işareti dizi parametresi : referans ile geçti; yazma işlemleri orijinal diziyi değiştirir.

Unutmayın - PHP, ve işareti olmayan dizi parametresine yazdığınız anda değer kopyalar . Anlamı bu copy-on-write. Size bu davranışın C kaynağını göstermek isterim, ama orada korkutucu. Daha iyi xdebug_debug_zval () kullanın .

Pascal MARTIN haklıydı. Kosta Kontos daha da öyleydi.

Cevap

Değişir.

Uzun versiyon

Sanırım bunu kendim için yazıyorum. Bir blogum falan olmalı ...

İnsanlar referanslardan (veya bu konuda işaretçilerden) bahsettiklerinde, genellikle bir logomachy ile sonuçlanırlar (sadece bu konuya bakın !).
PHP saygıdeğer bir dil olmak, (Bu yukarıdaki cevapların bir özeti olsa bile) karışıklık kadar eklemeniz gerektiğini düşündüm. Çünkü, iki kişi aynı anda haklı olsa da, kafalarını tek bir cevapta kırmak daha iyidir.

Öncelikle, siyah beyaz bir şekilde cevap vermezseniz bilgiç olmadığınızı bilmelisiniz . İşler "evet / hayır" dan daha karmaşıktır.

Gördüğünüz gibi, tüm by-value / by-reference şey, yöntem / işlev kapsamınızdaki bu diziyle tam olarak ne yaptığınızla çok ilgilidir: onu okumak veya değiştirmek mi?

PHP ne diyor? (aka "değişim-bilge")

Manuel bu (vurgu benim) diyor ki:

Varsayılan olarak, işlev bağımsız değişkenleri değere göre iletilir (böylece işlev içindeki bağımsız değişkenin değeri değiştirilirse , işlevin dışında değiştirilmez). Bir işlevin bağımsız değişkenlerini değiştirmesine izin vermek için , bunların başvuru ile iletilmesi gerekir .

Her zaman başvuru ile iletilen bir işleve bağımsız değişken edinmek için, işlev tanımındaki bağımsız değişken adına bir ve işareti (&) ekleyin

Anlayabildiğim kadarıyla, büyük, ciddi, dürüst-Tanrı programcıları referanslar hakkında konuştuğunda, genellikle bu referansın değerini değiştirmek hakkında konuşurlar . Ve bu tam olarak ne hakkında manuel görüşmeler var: hey, if you want to CHANGE the value in a function, consider that PHP's doing "pass-by-value".

Yine de bahsetmedikleri başka bir durum daha var: Ya bir şeyi değiştirmezsem - sadece oku?
Bir diziyi açıkça bir referansı işaretlemeyen bir yönteme geçirirseniz ve bu diziyi işlev kapsamında değiştirmezsek ne olur? Örneğin:

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

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

echo readAndDoStuffWithAnArray($x);

Okuyun, yolcum.

PHP aslında ne yapıyor? (diğer bir deyişle "bellek bakımından")

Aynı büyük ve ciddi programcılar, daha da ciddi hale geldiklerinde, referanslarla ilgili olarak "bellek optimizasyonları" hakkında konuşuyorlar. PHP de öyle. Çünkü PHP is a dynamic, loosely typed language, that uses copy-on-write and reference counting, bu yüzden .

BÜYÜK dizileri çeşitli işlevlere ve PHP'nin kopyalarını oluşturmak için geçmek ideal olmazdı (sonuçta "by by value" bunu yapar):

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

Peki, eğer bu gerçekten by-pass olsaydı, 3mb + RAMimiz olurdu, çünkü o dizinin iki kopyası var , değil mi?

Yanlış. $arrDeğişkeni değiştirmediğimiz sürece , bu, bellek açısından bir referanstır . Sadece görmüyorsun. Bu yüzden PHP , dahili ve açık (ve işareti ile) olanları ayırt etmek için konuşurken , kullanıcı-toprak referanslarından bahseder&$someVar .

Gerçekler

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

Üç (evet, üç) vaka ile geldim :
a) yöntem / işlev yalnızca dizi bağımsız değişkenini okur
b) yöntem / işlev dizi bağımsız değişkenini değiştirir
c) yöntem / işlev dizi bağımsız değişkeni açıkça bir başvuru ( Ve işareti)


İlk olarak, dizinin gerçekte ne kadar bellek yediğini görelim ( burada çalıştırın ):

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

O kadar bayt. Harika.

a) yöntem / işlev yalnızca dizi bağımsız değişkenini okur

Şimdi sadece bahsedilen diziyi argüman olarak okuyan bir fonksiyon yapalım ve okuma mantığının ne kadar bellek aldığını göreceğiz:

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

Tahmin etmek ister misin? 80 kazanıyorum! Kendiniz görün . Bu, PHP kılavuzunun atladığı bölümdür. Eğer $arrparam aslında geçirilen değer-ile-edildi, sen benzer bir şey görmek istiyorum 1331840bayt. Görünüşe göre $arrbir referans gibi davranıyor, değil mi? O da ondan olan bir iç biri - bir referanslar.

b) yöntem / işlev dizi bağımsız değişkenini değiştirir

Şimdi, bu parametreye okumak yerine, bu parametreye yazalım :

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

Yine, kendiniz görün bu durumda 1331840. So olmaya oldukça yakın olduğunu, benim için, ama, dizi olduğunu aslında kopyalanıyor $arr.

c) yöntem / işlev dizisi bağımsız değişkeni açıkça bir başvuru olarak işaretlenir (ve işareti ile)

Şimdi açık bir referans için bir yazma işleminin ne kadar bellek aldığını görelim ( burada çalıştırın ) - ve işareti fonksiyon imzasındaki notu not edin:

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

Benim bahse girerim, 200 maks. Yani bu , bir ve işareti olmayan paramdan okumak kadar yaklaşık bellek yiyor .


Bana bir bellek sızıntısı hata ayıklama birkaç saat kurtardı!
Ragen Dazs

2
Kosta Kontos: Bu çok önemli bir soru, bunu kabul edilen cevap olarak işaretlemelisiniz. Bununla birlikte, @nevvermind: Harika bir deneme, ancak lütfen bir üst TL; DR bölümü ekleyin.
AVIDeveloper

1
@nevvermind: Ben bir kısaltma groopy değilim, ana fark, Sonuçlar genellikle bir makalenin sonunda görünürken, TL; DR, uzun bir analizden geçmek yerine sadece kısa cevap vermek isteyenler için ilk satır olarak görünüyor. . Araştırmanız iyi ve bu eleştiri değil, sadece benim $ 00.02.
AVIDeveloper

1
Haklısın. Sonuçları en üste koydum. Ama yine de insanların herhangi bir sonuca varmadan önce her şeyi okurken tembel olmayı bırakmasını istiyorum . Kaydırma, şeylerin sırasını değiştirmekten rahatsız etmemiz için çok kolay.
nevvermind

1
PHP kod yıllar daha çok daha az sayı vermek çünkü PHP yıllar sonra daha verimli oldu sanırım :)
drzaus

14

Varsayılan olarak

  1. Temel öğeler değere göre geçirilir. Java'nın olası olmadığı, PHP'de dize ilkel
  2. İlkel dizileri değere göre geçirilir
  3. Nesneler referans ile iletilir
  4. Nesne dizileri değere (dizi) iletilir, ancak her nesne başvuru ile iletilir.

    <?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" } } 

Not: Bir optimizasyon olarak, her bir değer fonksiyonun içinde değiştirilinceye kadar referans olarak geçirilir. Değiştirilmişse ve değer referans olarak iletilmişse, kopyalanır ve kopya değiştirilir.


4
Bu cevap en üstte + 1'lenmiş olmalıdır. Diğer cevapların bahsetmediği belirsiz bir gotcha içeriyor: "4 - Nesnelerin dizileri değere (dizi) iletilir, ancak her nesne referans olarak iletilir." Bu yüzden başımı kaşıyordum!
Ağustos

@magallanes harika benim için de ilk olarak derecelendirilmelidir, bana sahip olduğum nesneler dizisinin bir sorununu netleştiriyorsunuz. Bir dizideki nesneyi iki dizi değişkeninden (orijinal ve kopya) birinde değiştirmenin herhangi bir yolu var mı?
fede72bari

5

PHP'de bir yönteme veya işleve bir dizi iletildiğinde, açıkça başvuru ile iletmediğiniz sürece, değere göre iletilir, şöyle:

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

İkinci sorunuzda, $bbir referans değil $a,$a .

İlk örnek gibi $a, aşağıdakileri yaparak referans verebilirsiniz :

$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

Bu iş parçacığı biraz daha eski ama burada yeni bir şeyle karşılaştım:

Bu kodu deneyin:

$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 parametresi için amp olmadığını ve yine de $ arr ['date'] değerini değiştirdiğini unutmayın. Bu, buradaki diğer açıklamalarla ve şimdiye kadar düşündüğümle gerçekten uyuşmuyor.

$ Params ['date'] nesnesini klonlarsam, 2. çıktı tarihi aynı kalır. Sadece bir dizeye ayarlarsam çıktıyı da etkilemez.


3
Dizi kopyalanır, ancak derin bir kopya değildir . Bu, sayılar ve dizeler gibi ilkel değerlerin $ param'a kopyalandığı, ancak nesneler için referansın klonlanan nesne yerine kopyalandığı anlamına gelir. $ arr, $ date için bir referans tutuyor ve kopyalanan $ $ params dizisi de öyle. Bu nedenle, değerini değiştiren $ params ['date'] üzerinde bir işlev çağırdığınızda, $ arr ['date'] ve $ date değerlerini de değiştirirsiniz. Bir dizeye $ params ['date'] ayarladığınızda, $ params'ın $ date referansını başka bir şeyle değiştirirsiniz.
ejegg

1

Cevaplardan birini genişletmek için, çok boyutlu dizilerin alt dizileri de referansla açıkça geçilmedikçe değere göre geçirilir.

<?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

Sonuç:

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

Aşağıdaki kod parçasının gösterdiği gibi, PHP dizilerinde açıkça başvuru ile iletmediğiniz sürece işlevlere varsayılan olarak değere geçirilir:

$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

İşte çıktı:

array(3) {
  [0]=>
  int(11)
  [1]=>
  int(22)
  [2]=>
  int(33)
}
array(3) {
  [0]=>
  int(66)
  [1]=>
  int(22)
  [2]=>
  int(33)
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.