web-dev-qa-db-fra.com

Les tableaux de PHP sont-ils passés par valeur ou par référence?

Lorsqu'un tableau est passé en tant qu'argument à une méthode ou à une fonction, est-il passé par référence?

Qu'en est-il de faire ceci:

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

Est-ce que $b est une référence à $a?

241
Frank

Pour la deuxième partie de votre question, voir le page de tableau du manuel , qui indique (en citant) :

L'affectation de tableau implique toujours la copie de valeur. Utilisez l'opérateur de référence pour copier un tableau par référence.

Et l'exemple donné:

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


Pour la première partie, le meilleur moyen d’en être sûr est d’essayer ;-)

Considérons cet exemple de code:

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

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

Ça va donner cette sortie:

array
  0 => int 10
  1 => int 20

Ce qui indique que la fonction n'a pas modifié le tableau "extérieur" qui a été passé en tant que paramètre: il est passé en copie et non en référence.

Si vous voulez qu'il soit passé par référence, vous devrez modifier la fonction de cette façon:

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

Et la sortie deviendra:

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

Comme, cette fois, le tableau a été passé "par référence".


N'hésitez pas à lire la section Références expliquées du manuel: elle devrait répondre à certaines de vos questions ;-)

259
Pascal MARTIN

En ce qui concerne votre première question, le tableau est passé par référence SAUF s'il est modifié dans la méthode/fonction que vous appelez. Si vous essayez de modifier le tableau dans la méthode/fonction, une copie de celui-ci est d'abord créée, puis seule la copie est modifiée. Cela donne l'impression que le tableau est passé par valeur alors qu'en réalité il ne l'est pas.

Par exemple, dans ce premier cas, même si vous ne définissez pas votre fonction pour accepter $ my_array par référence (en utilisant le caractère & dans la définition du paramètre), elle est toujours passée par référence (c'est-à-dire que vous ne gaspillez pas de mémoire. avec une copie inutile).

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
}

Cependant, si vous modifiez le tableau, une copie de celui-ci est faite en premier (ce qui utilise plus de mémoire mais ne modifie pas votre tableau d'origine).

function handle_array($my_array) {

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

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

FYI - Ceci est connu comme "copie différée" ou "copie sur écriture".

106
Kosta Kontos

TL; DR

a) la méthode/fonction lit uniquement l'argument du tableau => référence implicite (interne)
b) la méthode/fonction modifie l'argument du tableau => valeur
c) l'argument méthode/function tableau est explicitement marqué comme référence (avec une esperluette) => référence explicite (user-land)

Ou ca:
- Paramètre de tableau non-esperluette : transmis par référence; les opérations d'écriture modifient une nouvelle copie du tableau, copie créée lors de la première écriture;
- esperluette tableau param : transmis par référence; les opérations d'écriture modifient le tableau d'origine.

Rappelez-vous - PHP effectue une copie de valeur au moment où vous écrivez dans le paramètre tableau non-esperluette. C'est ce que copy-on-write signifie. J'adorerais vous montrer la source de ce comportement, mais cela fait peur. Mieux utiliser xdebug_debug_zval () .

Pascal MARTIN avait raison. Kosta Kontos l'était encore plus.

Répondre

Ça dépend.

Version longue

Je pense que je l'écris pour moi-même. Je devrais avoir un blog ou quelque chose comme ça ...

Chaque fois que les gens parlent de références (ou d'indicateurs, d'ailleurs), ils se retrouvent généralement dans une logomachie (il suffit de regarder ceci fil !).
PHP étant une langue vénérable, j’ai pensé que je devrais ajouter à la confusion (même si c’est un résumé des réponses ci-dessus). Parce que, bien que deux personnes puissent avoir raison en même temps, vous feriez mieux de ne leur donner qu'une idée en tête.

Tout d’abord, vous devez savoir que vous n’êtes pas un pédant si vous ne répondez pas en noir et blanc . Les choses sont plus compliquées que "oui/non".

Comme vous le verrez, le principe de valeur/valeur de référence est étroitement lié à ce que vous faites exactement avec ce tableau dans l'étendue de votre méthode/fonction: le lire ou le modifier?

Que dit PHP? (aka "changement-sage")

Le manuel dit ceci (c'est moi qui souligne):

Par défaut, les arguments de la fonction sont passés par la valeur (de sorte que si la valeur de l'argument dans la fonction est modifiée , cela ne change pas en dehors de la fonction). Pour permettre à une fonction de modifier ses arguments, ils doivent être passés par référence .

Pour qu'un argument d'une fonction soit toujours passé par référence, ajoutez une esperluette (&) au nom de l'argument dans la définition de la fonction.

Autant que je sache, lorsque de grands programmeurs sérieux, honnêtes devant Dieu, parlent de références, ils parlent généralement de altérer la valeur de cette référence . Et c’est exactement ce dont parle le manuel: hey, if you want to CHANGE the value in a function, consider that PHP's doing "pass-by-value".

Il y a un autre cas qu'ils ne mentionnent pas, cependant: et si je ne change rien - lis seulement?
Et si vous passiez un tableau à une méthode qui ne marque pas explicitement une référence et que nous ne changeons pas ce tableau dans la portée de la fonction? Par exemple.:

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

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

echo readAndDoStuffWithAnArray($x);

Continuez à lire, mon compagnon de route.

Qu'est-ce que PHP fait réellement? (aka "mémoire-sage")

Les mêmes grands et sérieux programmeurs, lorsqu'ils deviennent encore plus sérieux, parlent "d'optimisation de la mémoire" en ce qui concerne les références. Tout comme PHP. Parce que PHP is a dynamic, loosely typed language, that uses copy-on-write and reference counting, c'est pourquoi .

Ce ne serait pas idéal de passer d'énormes tableaux à diverses fonctions et de PHP d'en faire des copies (c'est ce que fait "la valeur de passage", après tout):

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

Eh bien, maintenant, si cela était en fait une valeur de passage, nous aurions 3 Mo + RAM partis, car il y a deux copies de ce tableau, à droite ?

Faux. Tant que nous ne changeons pas la variable $arr, c'est une référence en mémoire . Tu ne le vois pas. C'est pourquoi PHP mentions user-land références quand on parle de &$someVar, pour faire la distinction entre interne et explicite (avec esperluette).

Faits

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

Je suis venu avec trois (ouais, trois) cas:
a) la méthode/fonction lit uniquement l'argument array
b) la méthode/fonction modifie l'argument array
c) le paramètre method/function array est explicitement marqué comme référence (avec une esperluette)


Tout d’abord, voyons la quantité de mémoire réellement consommée par ce tableau (exécutez ici ):

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

C'est beaucoup d'octets. Génial.

a) la méthode/fonction ne lit que l'argument du tableau

Créons maintenant une fonction qui ne lit que ledit tableau en tant qu'argument et nous verrons combien de mémoire la logique de lecture prend:

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

Tu veux deviner? J'en ai 80! voyez par vous-même . C'est la partie que le manuel PHP omet. Si le paramètre $arr était réellement passé par valeur, vous verriez quelque chose de similaire à 1331840 octets. Il semble que $arr se comporte comme une référence, n'est-ce pas? C'est parce que est une référence - une référence interne.

b) la méthode/fonction modifie l'argument du tableau

Maintenant, écrivons dans ce paramètre, au lieu de le lire:

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

Encore une fois, voyez par vous-même , mais pour moi, c'est presque près d'être 1331840. Donc, dans ce cas, le tableau est en train d'être copié dans $arr.

c) le paramètre method/function array est explicitement marqué comme référence (avec une esperluette)

Voyons maintenant combien de mémoire une opération d'écriture sur une référence explicite prend (exécuter ici ) - notez l'esperluette dans la signature de la fonction:

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

Mon pari est que vous obtenez 200 max! Donc, cela consomme environ autant de mémoire que lire d'un paramètre non-esperluette .

72
nevvermind

Par défaut

  1. Les primitives sont passées par valeur. Peu probable en Java, la chaîne est primitive en PHP
  2. Les tableaux de primitives sont passés par valeur
  3. Les objets sont passés par référence
  4. Les tableaux d'objets sont passés par valeur (le tableau) mais chaque objet est passé par référence.

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

Remarque: En tant qu'optimisation, chaque valeur est transmise en tant que référence jusqu'à sa modification dans la fonction. S'il est modifié et que la valeur a été transmise par référence, il est copié et la copie est modifiée.

11
magallanes

Lorsqu'un tableau est passé à une méthode ou à une fonction en PHP, il est passé par valeur à moins que vous ne le passiez explicitement par référence, comme suit:

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

Dans votre deuxième question, $b n'est pas une référence à $a, mais une copie de $a.

Comme dans le premier exemple, vous pouvez référencer $a en procédant comme suit:

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

Ce fil est un peu plus ancien mais voici quelque chose que je viens de découvrir:

Essayez ce 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

Notez qu'il n'y a pas d'amplificateur pour le paramètre $ params et qu'il change quand même la valeur de $ arr ['date']. Cela ne correspond pas vraiment à toutes les autres explications ici et à ce que je pensais jusqu'à présent.

Si je clone l'objet $ params ['date'], la deuxième date sortie reste la même. Si je le règle simplement sur une chaîne, cela n'affectera pas non plus la sortie.

1
robbash

Pour étendre l’une des réponses, les sous-réseaux de tableaux multidimensionnels sont passés par valeur à moins d’être explicitement passés par référence.

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

Le résultat est:

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
K.Karamazen

Dans PHP, les tableaux _ sont passés aux fonctions par valeur par défaut, à moins que vous ne les transmettiez explicitement par référence, comme le montre l'extrait suivant:

$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

Voici la sortie:

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