PHP 中的数组是作为值复制还是作为对新变量的引用,以及何时传递给函数?

Are arrays in PHP copied as value or as reference to new variables, and when passed to functions?

提问人:Frank 提问时间:1/9/2010 最后编辑:SherylHohmanFrank 更新时间:4/7/2020 访问量:157988

问:

1) 当数组作为参数传递给方法或函数时,它是通过引用传递还是按值传递?

2)将数组分配给变量时,新变量是对原始数组的引用,还是新副本?
这样做怎么样:

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

是否引用 ?$b$a

PHP 数组 引用 传递 引用

评论

0赞 nawfal 11/18/2015
另请参阅 When-does-foreach-copy
5赞 user276648 11/22/2016
@MarlonJerezIsla:看起来只有当你在函数中修改它时,才会克隆数组。仍然来自其他语言,这似乎很奇怪。

答:

336赞 Pascal MARTIN 1/9/2010 #1

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

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

给出的例子:

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

因为,这一次,数组是“通过引用”传递的。


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

评论

0赞 Frank 1/9/2010
像 $a = &$this->a 这样的东西呢?$a现在是对 &this->a 的引用吗?
1赞 Pascal MARTIN 1/9/2010
当您使用 时,是的,它应该 - 请参阅 php.net/manual/en/...&
1赞 Heavy_Bullets 2/2/2014
天哪,我简直不敢相信这就是我遇到的问题......如果这是一个教训,请务必阅读 Offing 手册
2赞 Cheok Yan Cheng 5/29/2014
嗨,帕斯卡,我发现 Kosta Kontos 的答案似乎更准确。我做了一个简单的快速测试来确认他的发现 gist.github.com/anonymous/aaf845ae354578b74906 你也能评论一下他的发现吗?
1赞 Jeremy List 7/23/2015
这也是我遇到的问题:认为嵌套数组很奇怪,但实际上这就是数组赋值在 PHP 中的工作方式。
6赞 Corey Ballou 1/9/2010 #2

当一个数组被传递给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);
158赞 Kosta Kontos 3/16/2012 #3

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

例如,在第一种情况下,即使你没有将函数定义为通过引用接受 $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
}

仅供参考 - 这被称为“延迟复制”或“写入时复制”。

评论

9赞 Mario Awad 2/15/2013
这是一个超级有趣的信息!看起来是真的;但我找不到任何支持这一事实的官方文件。我们还需要知道哪些版本的 PHP 支持这种惰性复制概念。有人有更多信息吗?
8赞 Mario Awad 2/15/2013
更新后,找到一些官方文档,还是需要找到哪个版本的PHP支持延迟复制(他们在手册中称其为“写入时复制”):php.net/manual/en/internals2.variables.intro.php
14赞 Superfly 9/16/2015
这纯粹是PHP虚拟机的实现决定,而不是语言的一部分 - 它实际上对程序员来说是不可见的。出于性能原因,当然建议在写入时复制,但从程序员的角度来看,复制每个数组的实现没有区别,因此我们可以说语言语义指定了按值传递。
26赞 Mark Amery 1/2/2016
@Superfly当我想知道是否可以在不耗尽内存的情况下将我的 100MB 阵列通过数十个函数的堆栈时,这肯定会有所不同!你也许是对的,尽管如此,将语义称为按值传递是正确的,但撇开这些关于术语的狡辩不谈,这里提到的“实现细节”对现实世界中的PHP程序员来说确实很重要。
3赞 Dan King 2/23/2018
这还有另一个怪癖,这使得在考虑性能时意识到写时复制变得更加重要。您可能认为与按值传递相比,按引用传递数组可以节省内存(如果您不知道写入时复制),但实际上它可能会产生相反的效果!如果数组随后按值传递(通过您自己的或第三方代码),则 PHP 必须进行完整复制,否则它无法再跟踪引用计数!更多内容请见:stackoverflow.com/questions/21974581/...
101赞 nevvermind 4/8/2014 #4

TL的;博士

a) 方法/函数仅读取数组参数 => 隐式(内部)引用 b) 方法/函数修改数组参数 =>
c) 方法/函数数组参数显式标记为引用(带 & 号) =>显式(用户空间)引用

或者这样: - non-ampersand array param
通过引用传递;写入操作会更改数组的新副本,即在第一次写入时创建的副本;
- & 数组参数:通过引用传递;写入操作会更改原始数组。

请记住 - PHP 会在您写入非 & 数组参数的那一刻进行值复制。这就是这个意思。我很想向你展示这种行为的 C 源代码,但它在那里很可怕。最好使用 xdebug_debug_zval()。copy-on-write

帕斯卡·马丁是对的。科斯塔·康托斯(Kosta Kontos)更是如此。

这要视情况而定。

加长版

我想我是为自己写下来的。我应该有一个博客什么的......

每当人们谈论引用(或指针,就此而言),他们通常以徽标结束(看看这个线程
PHP是一种古老的语言,我认为我应该加起来混淆(尽管这是对上述答案的总结)。因为,虽然两个人可以同时是对的,但你最好把他们的脑袋放在一起,得出一个答案。

首先,你应该知道,如果你不以非黑即白的方式回答,你就不是一个书。事情比“是/否”更复杂。

正如你所看到的,整个按值/按引用的事情与你在方法/函数范围内对那个数组到底做了什么密切相关:读取它还是修改它?

PHP怎么说?(又名“Change-wise”)

手册是这样说的(强调我的):

默认情况下,函数参数按值传递(因此,如果 函数中参数的值被更改,它不会得到 在函数之外更改)。允许函数修改其 参数,它们必须通过引用传递

要对 函数总是通过引用传递,在 函数定义中的参数名称

据我所知,当大而严肃、诚实的程序员谈论引用时,他们通常会谈论改变引用的价值。而这正是手册中所说的:.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

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

现在,如果这实际上是按值传递的,我们会有一些 3mb+ 的 RAM 消失,因为该数组有两个副本,对吧?

错。只要我们不改变变量,就内存而言,这是一个参考。你只是没有看到它。这就是为什么 PHP 在谈论 时会提到用户域引用,以区分内部引用和显式(带有 & 符号)的引用。$arr&$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

那么多字节。伟大。

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

想猜吗?我得到 80 分!亲眼看看吧。这是PHP手册省略的部分。如果参数实际上是按值传递的,您会看到类似于字节的内容。这似乎表现得像一个参考,不是吗?那是因为它是一个参考 - 一个内部参考。$arr1331840$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);

再说一遍,你自己看看,但对我来说,这几乎是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);

我敢打赌,你最多得到 200 个!因此,这消耗的内存大约与从非 & 参数中读取的内存一样多。

评论

1赞 drzaus 5/5/2017
我想PHP在几年后变得更有效率了,因为你的代码板示例给出的数字要低得多:)
1赞 robbash 12/19/2014 #5

这个线程有点旧,但这是我刚刚遇到的一些事情:

请尝试以下代码:

$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参数没有 amp,它仍然会更改 $arr['date'] 的值。这与这里的所有其他解释以及我直到现在的想法并不完全匹配。

如果我克隆 $params['date'] 对象,则第二个输出的日期保持不变。如果我只是将其设置为字符串,它也不会影响输出。

评论

3赞 ejegg 1/17/2015
数组已复制,但它不是层复制。这意味着数字和字符串等基元值被复制到$param中,但对于对象,将复制引用而不是克隆对象。$arr保存对$date的引用,复制的数组$params也是如此。因此,当您在 $params['date'] 上调用更改其值的函数时,您也在更改 $arr['date'] 并$date。当您将 $params['date'] 设置为字符串时,您只是将 $params 对 $date 的引用替换为其他内容。
2赞 John Sonderson 2/1/2015 #6

在 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)
}
25赞 magallanes 4/2/2016 #7

默认情况下

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

注意:作为优化,每个值都作为引用传递,直到在函数中修改为止。如果它被修改并且该值是通过引用传递的,那么它将被复制并修改副本。

评论

5赞 augustin 6/20/2016
这个答案应该是 +1'ed 到顶部。它包含一个其他答案没有提到的晦涩难懂的陷阱:“4 - 对象数组按值(数组)传递,但每个对象都按引用传递。因为那个,我挠了挠头!
0赞 fede72bari 9/13/2019
@magallanes伟大也应该为我评为第一名,你为我澄清了我遇到的对象数组的麻烦。有没有办法在两个数组变量(原始变量和副本)之一中修改数组中的对象?
2赞 K.Karamazen 5/15/2019 #8

为了扩展其中一个答案,多维数组的子数组也按值传递,除非通过引用显式传递。

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