PHP中的数组是通过值还是通过引用传递的?

Fra*_*ank 245 php arrays reference pass-by-reference pass-by-value

当一个数组作为参数传递给方法或函数时,它是通过引用传递的吗?

这样做怎么样:

$a = array(1,2,3);
$b = $a;
Run Code Online (Sandbox Code Playgroud)

$b参考$a

Pas*_*TIN 262

对于问题的第二部分,请参阅手册数组页面,其中说明(引用):

数组赋值始终涉及值复制.使用引用运算符通过引用复制数组.

给出的例子:

<?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
?>
Run Code Online (Sandbox Code Playgroud)


对于第一部分,最好的方法是尝试;-)

考虑这个代码示例:

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

$arr = array(10, 20);
my_func($arr);
var_dump($arr);
Run Code Online (Sandbox Code Playgroud)

它会给出这个输出:

array
  0 => int 10
  1 => int 20
Run Code Online (Sandbox Code Playgroud)

这表明函数没有修改作为参数传递的"外部"数组:它作为副本传递,而不是引用.

如果你想通过引用传递它,你将不得不修改函数,这样:

function my_func(& $a) {
    $a[] = 30;
}
Run Code Online (Sandbox Code Playgroud)

输出将变为:

array
  0 => int 10
  1 => int 20
  2 => int 30
Run Code Online (Sandbox Code Playgroud)

因为,这次,数组已经"通过引用"传递.


不要犹豫,阅读手册中的参考文献解释部分:它应该回答你的一些问题;-)

  • 嗨Pascal,我发现Kosta Kontos的答案似乎更准确.我做了一个简单的快速测试来确认他的发现https://gist.github.com/anonymous/aaf845ae354578b74906你能评论他的发现吗? (2认同)

Kos*_*tos 113

关于你的第一个问题,数组是通过引用传递的,除非它在你调用的方法/函数中被修改.如果您尝试在方法/函数中修改数组,则首先创建它的副本,然后仅修改副本.这使得它看起来好像数组是按值传递的,而事实上并非如此.

例如,在第一种情况下,即使您没有通过引用定义函数来接受$ 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
}
Run Code Online (Sandbox Code Playgroud)

但是,如果修改数组,则首先创建它的副本(使用更多内存但不会影响原始数组).

function handle_array($my_array) {

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

    // ... $my_array effectively passed by value since requires local copy
}
Run Code Online (Sandbox Code Playgroud)

仅供参考 - 这被称为"懒惰副本"或"写入时复制".

  • @Superfly当我想知道我是否可以通过几十个函数堆栈传递我的100MB阵列而不会耗尽内存时,它确实有所作为!你可能是正确的,因为调用语义值传递是正确的,但是除了术语之外的这些狡辩,这里提到的"实现细节"肯定对现实世界中的PHP程序员很重要. (11认同)
  • 更新,发现一些官方文档,仍然需要找到哪个版本的PHP支持延迟拷贝(他们称之为"手写时拷贝"):http://php.net/manual/en/internals2.variables.intro. PHP (8认同)
  • 这是一条非常有趣的信息!看起来这是真的; 但我找不到任何支持这一事实的官方文件.我们还需要知道哪些版本的PHP支持这种懒惰的副本概念.任何人有更多的信息? (7认同)
  • 这纯粹是PHP虚拟机的实现决策,而不是语言的一部分 - 程序员实际上看不到它.出于性能原因,肯定建议使用写时复制,但复制每个数组的实现与程序员的角度没有区别,因此我们可以说语言语义指定了值传递. (4认同)
  • 这还有一个怪癖,这使得在考虑性能时意识到写时复制变得更加重要。您可能认为通过引用 _saves_ 内存传递数组与通过值传递相比(如果您不知道写时复制),但它实际上可以具有 _相反_ 效果!如果数组 _subsequently_ 按值传递(由您自己的或第 3 方代码),PHP 然后 _has_ 制作完整副本,否则它无法再跟踪引用计数!更多信息:/sf/ask/1538220701/ (3认同)
  • 虽然您所解释的是正确的,但您使用的词语并不准确。在PHP中,所有内容都通过** VALUE **传递。总是!除非您使用`&`显式定义函数签名!但这并不意味着每次您将变量传递给函数或进行赋值时,PHP都会在内部创建ZVAL的真实副本。PHP使用引用计数器,但这并不是引用本身在PHP代码中的含义。这只是一点点混乱! (2认同)

nev*_*ind 75

TL; DR

a)方法/函数只读取数组参数=> 隐式(内部)引用
b)方法/函数修改数组参数=>
c)方法/函数数组参数显式标记为引用(带符号) => 显式(用户 - 土地)参考

或者:
- 非&符号数组参数:通过引用传递; 写操作改变了数组的新副本,在第一次写入时创建的副本;
- &符号数组参数:通过引用传递; 写入操作改变了原始数组.

记住 - 当你写入非&符号数组参数时,PHP会进行值复制.这copy-on-write意味着什么.我很想向你展示这种行为的C源,但它在那里很可怕.更好地使用xdebug_debug_zval().

帕斯卡马丁是对的.Kosta Kontos更是如此.

回答

这取决于.

长版

我想我正在为自己写这篇文章.我应该有博客或其他东西......

每当人们谈论引用(或指针,就此而言)时,它们通常会陷入一种逻辑上(只看这个线程!).
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);
Run Code Online (Sandbox Code Playgroud)

继续阅读,我的同路人.

PHP实际上做了什么?(又名"记忆")

相同的大而严肃的程序员,当他们变得更加严肃时,他们会谈论有关引用的"内存优化".PHP也是如此.因为PHP is a dynamic, loosely typed language, that uses copy-on-write and reference counting,这就是原因.

将HUGE数组传递给各种函数并不是理想的,并且PHP要复制它们(毕竟这就是"传值"所做的):

<?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);
Run Code Online (Sandbox Code Playgroud)

那么现在,如果这实际上是按值传递,我们会有3mb + RAM消失,因为该阵列有两个副本,对吧?

错误.只要我们不改变$arr变量,那就是内存方面的参考.你只是没有看到它.这就是为什么PHP 在谈论时提到 用户土地引用&$someVar,以区分内部和显式(与&符号).

事实

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

我想出了三个(是的,三个)情况:
a)方法/函数只读取数组参数
b)方法/函数修改数组参数
c)方法/函数数组参数被明确标记为引用(带有号)


首先,让我们看看阵列实际吃多少内存(在这里运行):

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

那很多字节.大.

a)方法/函数只读取数组参数

现在让我们创建一个只读取所述数组作为参数的函数,我们将看到读取逻辑占用多少内存:

<?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);
Run Code Online (Sandbox Code Playgroud)

想猜?我80岁了!亲眼看看.这是PHP手册省略的部分.如果$arrparam实际上是按值传递的,那么你会看到类似于1331840字节的东西.它似乎$arr表现得像一个参考,不是吗?那是因为它一个参考 - 一个内部参考.

b)方法/函数修改数组参数

现在,让我们一下这个参数,而不是从中读取:

<?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);
Run Code Online (Sandbox Code Playgroud)

再次,看到自己,但对我来说,那是相当接近所以在这种情况下是1331840.,该阵列实际上被复制到$arr.

c)方法/函数数组参数被明确标记为引用(带符号)

现在让我们看看显式引用的写操作需要多少内存(在这里运行) - 注意函数签名中的&符号:

<?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);
Run Code Online (Sandbox Code Playgroud)

我打赌你最多可以获得200分!因此,这与从非&符号参数读取的内存大致相同.

  • Kosta Kontos:这是一个非常重要的问题,您应该将其标记为已接受的答案。也就是说,@nevvermind:很棒的文章,但请包括一个顶级的 TL;DR 部分。 (2认同)

mag*_*nes 13

默认情况下

  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" } } 
    
    Run Code Online (Sandbox Code Playgroud)

注意:作为优化,每个值都作为引用传递,直到在函数内部进行修改.如果它被修改并且值通过引用传递,那么它将被复制并修改副本.

  • 这个答案应该在顶部+ 1'.它包含一个不起眼的问题,其他答案没有提到:"4 - 对象数组按值(数组)传递,但每个对象都通过引用传递." 我因为那个而挠挠脑袋! (4认同)

Cor*_*lou 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);
Run Code Online (Sandbox Code Playgroud)

在您的第二个问题中,$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);
Run Code Online (Sandbox Code Playgroud)