Werden Arrays in PHP als Wert oder als Referenz auf neue Variablen kopiert und an Funktionen übergeben?

259

1) Wenn ein Array als Argument an eine Methode oder Funktion übergeben wird, wird es als Referenz oder als Wert übergeben?

2) Wenn Sie einer Variablen ein Array zuweisen, verweist die neue Variable auf das ursprüngliche Array oder handelt es sich um eine neue Kopie?
Was ist damit:

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

Ist $bein Verweis auf $a?

Frank
quelle
Siehe auch Wann-tut-für-
jede
3
@MarlonJerezIsla: Es sieht so aus, als ob das Array nur geklont wird, wenn Sie es innerhalb der Funktion ändern. Es kommt immer noch aus anderen Sprachen und scheint seltsam.
user276648

Antworten:

276

Den zweiten Teil Ihrer Frage finden Sie auf der Array-Seite des Handbuchs , auf der Folgendes angegeben ist (Zitat) :

Bei der Array-Zuweisung wird immer ein Wert kopiert. Verwenden Sie den Referenzoperator, um ein Array als Referenz zu kopieren.

Und das gegebene Beispiel:

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


Für den ersten Teil ist der beste Weg, um sicher zu sein, es zu versuchen ;-)

Betrachten Sie dieses Codebeispiel:

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

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

Es wird diese Ausgabe geben:

array
  0 => int 10
  1 => int 20

Dies zeigt an, dass die Funktion das als Parameter übergebene "externe" Array nicht geändert hat: Es wird als Kopie und nicht als Referenz übergeben.

Wenn Sie möchten, dass es als Referenz übergeben wird, müssen Sie die Funktion folgendermaßen ändern:

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

Und die Ausgabe wird:

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

Da dieses Mal das Array "als Referenz" übergeben wurde.


Zögern Sie nicht, den Abschnitt " Erklärte Referenzen " des Handbuchs zu lesen : Er sollte einige Ihrer Fragen beantworten ;-)

Pascal MARTIN
quelle
was ist mit so etwas wie $ a = & $ this-> a. Ist $ a jetzt ein Verweis auf & this-> a?
Frank
1
Während Sie das verwenden &, sollte es - siehe php.net/manual/en/…
Pascal MARTIN
1
Heilige Kuh, ich kann nicht glauben, dass dies das Problem ist, das ich hatte ... sollte dies eine Lektion sein, lesen Sie immer das Handbuch
Heavy_Bullets
2
Hallo Pascal, ich fand, dass Kosta Kontos 'Antwort genauer zu sein scheint. Ich mache einen einfachen Schnelltest, um seinen Befund zu bestätigen. Gist.github.com/anonymous/aaf845ae354578b74906 Können Sie auch seinen Befund kommentieren?
Cheok Yan Cheng
1
Dies ist das Problem, das ich auch hatte: Ich dachte, es sei etwas Seltsames an verschachtelten Arrays, aber es war tatsächlich nur die Funktionsweise der Array-Zuweisung in PHP.
Jeremy List
120

In Bezug auf Ihre erste Frage wird das Array als Referenz übergeben, sofern es nicht innerhalb der von Ihnen aufgerufenen Methode / Funktion geändert wird. Wenn Sie versuchen, das Array innerhalb der Methode / Funktion zu ändern, wird zuerst eine Kopie davon erstellt, und dann wird nur die Kopie geändert. Dies lässt es so aussehen, als ob das Array als Wert übergeben wird, obwohl dies tatsächlich nicht der Fall ist.

In diesem ersten Fall wird Ihre Funktion zwar nicht so definiert, dass sie $ my_array als Referenz akzeptiert (indem Sie das Zeichen & in der Parameterdefinition verwenden), sie wird jedoch weiterhin als Referenz übergeben (dh Sie verschwenden keinen Speicher mit einer unnötigen Kopie).

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
}

Wenn Sie das Array jedoch ändern, wird zuerst eine Kopie davon erstellt (die mehr Speicher benötigt, Ihr ursprüngliches Array jedoch nicht beeinflusst).

function handle_array($my_array) {

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

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

Zu Ihrer Information - dies ist als "Lazy Copy" oder "Copy-on-Write" bekannt.

Kosta Kontos
quelle
8
Dies ist eine super interessante Information! Sieht aus wie es wahr ist; aber ich konnte keine offizielle Dokumentation finden, die diese Tatsache stützt. Wir müssen auch wissen, welche PHP-Versionen dieses Lazy-Copy-Konzept unterstützen. Hat jemand mehr Infos?
Mario Awad
8
Update, einige offizielle Dokumentation gefunden, müssen noch herausfinden, welche Version von PHP Lazy Copy unterstützt (sie nennen es "Copy on Write" im Handbuch): php.net/manual/en/internals2.variables.intro.php
Mario Awad
7
Dies ist lediglich eine Implementierungsentscheidung der virtuellen PHP-Maschine und nicht Teil der Sprache - sie ist für den Programmierer nicht sichtbar. Copy-on-Write wird aus Leistungsgründen sicherlich empfohlen, aber eine Implementierung, die jedes Array kopiert, hat aus Sicht des Programmierers keinen Unterschied, sodass wir sagen können, dass die Sprachsemantik Pass-by-Value angibt.
Superfly
14
@ Superfly Es macht sicherlich einen Unterschied, wenn ich wissen möchte, ob ich mein 100-MB-Array durch einen Stapel von Dutzenden von Funktionen leiten kann, ohne dass der Speicher knapp wird! Sie mögen Recht haben, dass es dennoch richtig ist, die Semantik als Wertübergabe zu bezeichnen, aber abgesehen von solchen Streitigkeiten über die Terminologie ist das hier erwähnte "Implementierungsdetail" für PHP-Programmierer in der realen Welt sicherlich von Bedeutung.
Mark Amery
3
Dies hat noch eine weitere Besonderheit, die es noch wichtiger macht, sich des Copy-on-Write bewusst zu werden, wenn man über Leistung nachdenkt. Sie könnten denken, dass das Übergeben von Arrays als Referenz Speicherplatz spart im Vergleich zum Übergeben von Werten (wenn Sie nichts über Copy-on-Write wissen), aber es kann tatsächlich den gegenteiligen Effekt haben! Wenn das Array wird anschließend durch Wert (mit dem eigenen oder 3rd - Party - Code) übergibt, PHP dann hat eine vollständige Kopie zu machen , oder es kann nicht mehr die Referenzzähler verfolgen! Mehr hier: stackoverflow.com/questions/21974581/…
Dan King
80

TL; DR

a) Die Methode / Funktion liest nur das Array-Argument => implizite (interne) Referenz
b) Die Methode / Funktion ändert das Array-Argument => Wert
c) Das Methode / Funktion-Array-Argument wird explizit als Referenz markiert (mit einem kaufmännischen Und). => explizite (Benutzerland-) Referenz

Oder dies:
- Array-Parameter ohne kaufmännisches Und : als Referenz übergeben; Die Schreibvorgänge ändern eine neue Kopie des Arrays, eine Kopie, die beim ersten Schreiben erstellt wird.
- kaufmännisches Und-Array-Parameter : als Referenz übergeben; Die Schreibvorgänge ändern das ursprüngliche Array.

Denken Sie daran, dass PHP in dem Moment, in dem Sie in den Array-Parameter ohne kaufmännisches Und schreiben , eine Wertkopie erstellt. Das copy-on-writeheißt was . Ich würde Ihnen gerne die C-Quelle dieses Verhaltens zeigen, aber es ist dort beängstigend. Verwenden Sie besser xdebug_debug_zval () .

Pascal MARTIN hatte recht. Kosta Kontos war es noch mehr.

Antworten

Es hängt davon ab, ob.

Lange Version

Ich glaube, ich schreibe das für mich auf. Ich sollte einen Blog haben oder so ...

Wenn Leute von Referenzen (oder Zeigern) sprechen, geraten sie normalerweise in eine Logomachie (schauen Sie sich nur diesen Thread an !).
Da PHP eine ehrwürdige Sprache ist, dachte ich, ich sollte die Verwirrung noch verstärken (obwohl dies eine Zusammenfassung der obigen Antworten ist). Denn obwohl zwei Personen gleichzeitig Recht haben können, ist es besser, wenn Sie nur ihre Köpfe zu einer Antwort zusammenbrechen.

Zunächst sollten Sie wissen, dass Sie kein Pedant sind, wenn Sie nicht schwarz-weiß antworten . Die Dinge sind komplizierter als "Ja / Nein".

Wie Sie sehen werden, hängt die gesamte Sache nach Wert / Referenz sehr stark davon ab, was genau Sie mit diesem Array in Ihrem Methoden- / Funktionsumfang tun: Lesen oder Ändern?

Was sagt PHP? (auch bekannt als "veränderungsmäßig")

Das Handbuch sagt dies (Hervorhebung von mir):

Standardmäßig werden Funktionsargumente als Wert übergeben (sodass der Wert des Arguments innerhalb der Funktion nicht außerhalb der Funktion geändert wird , wenn er geändert wird). Damit eine Funktion ihre Argumente ändern kann , müssen sie als Referenz übergeben werden .

Stellen Sie dem Argumentnamen in der Funktionsdefinition ein kaufmännisches Und (&) voran, damit ein Argument für eine Funktion immer als Referenz übergeben wird

Soweit ich das beurteilen kann, sprechen große, seriöse und ehrliche Programmierer, wenn sie über Referenzen sprechen, normalerweise davon , den Wert dieser Referenz zu ändern . Und genau darüber spricht das Handbuch : hey, if you want to CHANGE the value in a function, consider that PHP's doing "pass-by-value".

Es gibt noch einen anderen Fall, den sie nicht erwähnen: Was ist, wenn ich nichts ändere - einfach lesen?
Was passiert, wenn Sie ein Array an eine Methode übergeben, die keine Referenz explizit markiert, und wir dieses Array im Funktionsumfang nicht ändern? Z.B:

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

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

echo readAndDoStuffWithAnArray($x);

Lesen Sie weiter, mein Mitreisender.

Was macht PHP eigentlich? (auch bekannt als "gedächtnismäßig")

Dieselben großen und seriösen Programmierer sprechen, wenn sie noch ernsthafter werden, von "Speicheroptimierungen" in Bezug auf Referenzen. PHP auch. Weil PHP is a dynamic, loosely typed language, that uses copy-on-write and reference counting, das ist , warum .

Es wäre nicht ideal, RIESIGE Arrays an verschiedene Funktionen zu übergeben und PHP, um Kopien davon zu erstellen (genau das macht schließlich "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);

Nun, wenn dies tatsächlich ein Pass-by-Wert wäre, hätten wir 3 MB + RAM verloren, weil es zwei Kopien dieses Arrays gibt, oder?

Falsch. Solange wir die $arrVariable nicht ändern , ist dies eine Referenz in Bezug auf den Speicher . Du siehst es einfach nicht. Deshalb PHP erwähnt benutzer Land Referenzen , wenn es um Gespräche &$someVarzwischen internen und explizit (mit Ampersand) zu unterscheiden.

Fakten

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

Ich habe drei (ja, drei) Fälle gefunden:
a) Die Methode / Funktion liest nur das Array-Argument.
B) Die Methode / Funktion ändert das Array-Argument.
C) Das Array-Argument der Methode / Funktion ist explizit als Referenz markiert (mit einem Et-Zeichen)


Lassen Sie uns zunächst sehen, wie viel Speicher das Array tatsächlich verbraucht ( hier ausführen ):

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

So viele Bytes. Toll.

a) Die Methode / Funktion liest nur das Array-Argument

Lassen Sie uns nun eine Funktion erstellen, die nur das Array als Argument liest , und wir werden sehen, wie viel Speicher die Leselogik benötigt:

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

Willst du raten? Ich bekomme 80! Überzeugen Sie sich selbst . Dies ist der Teil, den das PHP-Handbuch weglässt. Wenn der $arrParameter tatsächlich als Wert übergeben wurde, sehen Sie etwas Ähnliches wie 1331840Bytes. Es scheint, dass $arrsich das wie eine Referenz verhält, nicht wahr? Das liegt daran, dass es sich um eine interne Referenz handelt.

b) Die Methode / Funktion ändert das Array-Argument

Schreiben wir nun an diesen Parameter, anstatt daraus zu lesen:

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

Auch hier sehen Sie selbst , aber für mich, dass in diesem Fall zu sein 1331840. So ziemlich nah dran ist, das Array ist tatsächlich kopiert werden $arr.

c) Das Argument für das Methoden- / Funktionsarray wird explizit als Referenz markiert (mit einem kaufmännischen Und).

Lassen Sie uns nun sehen, wie viel Speicher eine Schreiboperation für eine explizite Referenz benötigt ( hier ausführen ). Beachten Sie das kaufmännische Und in der Funktionssignatur:

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

Meine Wette ist, dass Sie maximal 200 bekommen! Dies verbraucht also ungefähr so ​​viel Speicher wie das Lesen von einem Parameter ohne kaufmännisches Und .

nieverstand
quelle
Ersparte mir ein paar Stunden beim Debuggen eines Speicherverlusts!
Ragen Dazs
2
Kosta Kontos: Dies ist eine so wichtige Frage, dass Sie dies als akzeptierte Antwort markieren sollten. Das heißt, @nevvermind: Großartiger Aufsatz, aber bitte fügen Sie einen Top-TL; DR-Abschnitt hinzu.
AVIDeveloper
1
@nevvermind: Ich bin kein Akronym-Groopy. Der Hauptunterschied besteht darin, dass Schlussfolgerungen normalerweise am Ende eines Artikels erscheinen, während TL; DR als erste Zeile für diejenigen erscheint, die nur eine kurze Antwort benötigen, anstatt eine lange Analyse durchzuführen . Ihre Forschung ist gut und dies ist keine Kritik, nur meine $ 00.02.
AVIDeveloper
1
Du hast recht. Ich habe die Schlussfolgerungen an die Spitze gesetzt. Aber ich möchte immer noch, dass die Leute aufhören, faul zu sein, wenn sie das Ganze lesen, bevor sie zu einer Schlussfolgerung gelangen . Das Scrollen ist zu einfach, als dass wir uns die Mühe machen könnten, die Reihenfolge der Dinge zu ändern.
Nevvermind
1
Ich denke, PHP ist Jahre später effizienter geworden, weil Ihre Codepad-Beispiele viel niedrigere Zahlen ergeben :)
drzaus
14

Standardmäßig

  1. Grundelemente werden als Wert übergeben. Für Java ist es unwahrscheinlich, dass der String in PHP primitiv ist
  2. Arrays von Grundelementen werden als Wert übergeben
  3. Objekte werden als Referenz übergeben
  4. Arrays von Objekten werden als Wert (das Array) übergeben, aber jedes Objekt wird als Referenz übergeben.

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

Hinweis: Als Optimierung wird jeder einzelne Wert als Referenz übergeben, bis er innerhalb der Funktion geändert wird. Wenn es geändert wurde und der Wert als Referenz übergeben wurde, wird es kopiert und die Kopie wird geändert.

Magallane
quelle
4
Diese Antwort sollte + 1 nach oben sein. Es enthält ein obskures Gotcha, das in anderen Antworten nicht erwähnt wird: "4 - Arrays von Objekten werden als Wert (das Array) übergeben, aber jedes Objekt wird als Referenz übergeben." Ich habe mir deswegen den Kopf gekratzt!
August
@magallanes great sollte auch für mich zuerst bewertet werden, du klärst mir ein Problem mit dem Objektarray, das ich hatte. Gibt es eine Möglichkeit, ein Objekt in einem Array nur in einer der beiden Array-Variablen (Original und Kopie) zu ändern?
fede72bari
5

Wenn ein Array an eine Methode oder Funktion in PHP übergeben wird, wird es als Wert übergeben, es sei denn, Sie übergeben es explizit als Referenz, wie folgt:

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

In Ihrer zweiten Frage $bhandelt es sich nicht um einen Verweis auf $a, sondern um eine Kopie von $a.

Ähnlich wie im ersten Beispiel können Sie auf $aFolgendes verweisen :

$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);
Corey Ballou
quelle
1

Dieser Thread ist etwas älter, aber hier ist mir etwas begegnet:

Versuchen Sie diesen Code:

$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

Beachten Sie, dass für den Parameter $ params kein Verstärker vorhanden ist und der Wert von $ arr ['date'] geändert wird. Dies stimmt nicht wirklich mit allen anderen Erklärungen hier und dem überein, was ich bisher gedacht habe.

Wenn ich das Objekt $ params ['date'] klone, bleibt das 2. ausgegebene Datum gleich. Wenn ich es nur auf einen String setze, wirkt sich das auch nicht auf die Ausgabe aus.

Robbash
quelle
3
Das Array wird kopiert, aber es ist keine tiefe Kopie. Dies bedeutet, dass primitive Werte wie Zahlen und Zeichenfolgen in $ param kopiert werden. Bei Objekten wird jedoch die Referenz kopiert, anstatt dass das Objekt geklont wird. $ arr enthält einen Verweis auf $ date, ebenso wie das kopierte Array $ params. Wenn Sie also eine Funktion für $ params ['date'] aufrufen, die ihren Wert ändert, ändern Sie auch $ arr ['date'] und $ date. Wenn Sie $ params ['date'] auf eine Zeichenfolge setzen, ersetzen Sie lediglich den Verweis von $ params auf $ date durch etwas anderes.
Ejegg
1

Um eine der Antworten zu erweitern, werden auch Subarrays mehrdimensionaler Arrays als Wert übergeben, sofern sie nicht explizit als Referenz übergeben werden.

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

Das Ergebnis ist:

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)
}
K. Karamazen
quelle
0

In PHP werden Arrays standardmäßig standardmäßig an Funktionen übergeben, es sei denn, Sie übergeben sie explizit als Referenz, wie das folgende Snippet zeigt:

$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

Hier ist die Ausgabe:

array(3) {
  [0]=>
  int(11)
  [1]=>
  int(22)
  [2]=>
  int(33)
}
array(3) {
  [0]=>
  int(66)
  [1]=>
  int(22)
  [2]=>
  int(33)
}
John Sonderson
quelle