PHP中的数组在传递给函数时会复制为值还是对新变量的引用?


259

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

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

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

$b参考$a吗?


另请参见“ 何时进行复制”
nawfal

3
@MarlonJerezIsla:看起来只有在函数内部修改数组时,才会克隆该数组。仍然来自其他语言,看起来很奇怪。
user276648 '16

Answers:


276

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

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

并给出示例:

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

同样,这次,该数组已“通过引用”传递。


不要犹豫,阅读手册的“ 参考说明”部分:它应该回答您的一些问题;-)


$ a =&$ this-> a之类的东西呢?现在$ a是对&this-> a的引用吗?
法兰克

1
当你正在使用的&,是的,它应该-看到php.net/manual/en/...
帕斯卡尔·马丁

1
圣牛,我
简直

2
您好Pascal,我发现Kosta Kontos的回答似乎更准确。我做了一个简单的快速测试,以确认他的发现。gist.github.com/anonymous/aaf845ae354578b74906你能对他的发现发表评论吗?
Cheok Yan Cheng 2014年

1
这也是我遇到的问题:认为这与嵌套数组有关,但实际上只是PHP中数组分配的工作方式。
杰里米·

120

关于第一个问题,除非您在要调用的方法/函数中对其进行了修改,否则数组将通过引用传递。如果您尝试在方法/函数中修改数组,则先创建其副本,然后再修改该副本。这使得好像数组实际上是通过值传递的,而实际上不是。

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

仅供参考-这就是所谓的“惰性复制”或“写时复制”。


8
这是一条非常有趣的信息!看起来是真的。但是我找不到任何支持此事实的官方文档。我们还需要知道哪些PHP版本支持此惰性复制概念。有人有更多信息吗?
Mario Awad

8
更新,找到了一些官方文档,仍然需要查找哪个版本的PHP支持延迟复制(在手册中将
Mario Awad

7
这纯粹是PHP虚拟机的实现决定,而不是语言的一部分-程序员实际上并不可见。出于性能原因,当然建议使用写时复制,但是从程序员的角度来看,复制每个数组的实现没有什么区别,因此可以说语言语义指定了按值传递。
Superfly

14
@Superfly当我想知道是否可以通过数十个函数堆栈传递100MB数组而不会耗尽内存时,肯定会有所不同!您可能是对的,尽管称呼语义传递值是正确的,但是撇开术语上的这些疑问,这里提到的“实现细节”对于现实世界中的PHP程序员确实很重要。
Mark Amery'1

3
这还有另一个怪癖,那就是在考虑性能时,意识到写时复制就显得尤为重要。您可能会认为按引用传递数组与按值传递相比节省了内存(如果您不知道写时复制),但实际上可能产生相反的效果!如果数组随后通过值(通过您自己的代码或第三方代码)传递,则PHP 必须进行完整复制,否则它将无法跟踪引用计数!更多信息,请访问:stackoverflow.com/questions/21974581/…–
丹·金

80

TL; DR

a)方法/函数仅读取数组参数=> 隐式(内部)引用
b)方法/函数修改数组参数=>
c)方法/函数数组参数被显式标记为引用(与号) => 明确的(用户区域)引用

或这样:
- 非&数组参数:通过引用传递;写入操作会更改阵列的新副本,即在第一次写入时创建的副本;
-与符号数组参数:通过引用传递;写入操作会更改原始数组。

请记住- 当您写入非符号数组参数时,PHP会进行值复制。那是什么copy-on-write意思 我很想向您展示此行为的C源代码,但是其中的内容令人恐惧。最好使用xdebug_debug_zval()

Pascal MARTIN是对的。Kosta Kontos更是如此。

回答

这取决于。

长版

我想我正在为自己写下这些。我应该有一个博客之类的东西。

每当人们谈论引用(或指针)时,它们通常都会以通俗的形式出现(只需看一下该线程!)。
PHP是一种古老的语言,我认为我应该加倍混淆(即使这是上述答案的总结)。因为,尽管两个人可能同时是对的,但最好还是将他们的头脑拼凑成一个答案。

首先,如果您不以黑白方式回答,您应该知道自己不是学徒。事情比“是/否”要复杂得多。

就像您将看到的那样,整个按值/按引用的事情与您在方法/函数范围内对该数组的确切用途有很大关系:读取还是修改它?

PHP说什么?(又名“明智变化”)

手册说,这(重点煤矿):

默认情况下,函数参数是按值传递的(因此,如果函数中参数的值发生更改,则不会在函数外部进行更改)。要允许函数修改其参数,必须通过reference传递它们。

要使函数的参数始终通过引用传递,请在函数定义中的参数名称前加上“&”号

据我所知,当大型,认真,诚实的程序员谈论引用时,他们通常谈论的是改变引用的价值。而这正是手册所讨论的内容: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消失了,因为该数组有两个副本,对吗?

错误。只要我们不更改$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

这么多字节。大。

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手册省略的部分。如果$arr参数实际上是按值传递的,则将看到类似于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);

再次,看到自己,但对我来说,那是相当接近在这种情况下是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!因此,这消耗的内存与从非“&”参数读取的内存差不多


在调试内存泄漏时节省了我几个小时!
拉根达兹(Ragen Dazs)

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

1
@nevvermind:我不是首字母缩略词,主要区别是结论通常出现在文章结尾,而TL; DR则是那些只需要简短回答而不需要冗长分析的人的第一行。您的研究很好,这不是批评,只是我的$ 00.02。
AVIDeveloper

1
你是对的。我把结论放在最上面。但是我仍然希望人们在得出任何结论之前不要懒惰地阅读整本书。滚动对于我们来说太容易了,不方便更改事物的顺序。
nevvermind

1
我想PHP会在几年后变得更有效率,因为您的键盘示例给出的数字要低得多:)
drzaus

14

默认

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

注意:作为优化,将每个值作为引用传递,直到在函数内部对其进行修改为止。如果对其进行了修改并且该值是通过引用传递的,则将其复制并修改副本。


4
此答案应该在顶部+1。它包含一个晦涩的陷阱,其他答案未提及:“ 4-对象数组是通过值(数组)传递的,但是每个对象都是通过引用传递的。” 我正因为那个而挠头!
奥古斯汀

@magallanes great应该也对我来说是第一位,您向我说明了我拥有的对象数组的麻烦。是否可以仅在两个数组变量(原始数组和副本数组)之一中修改数组中的对象?
fede72bari

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

在第二个问题中,$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);

1

这个线程有点旧,但是在这里我碰到了一些东西:

试试这个代码:

$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参数没有放大器,但仍会更改$ arr ['date']的值。这与这里的所有其他解释以及我到目前为止的想法均不完全一致。

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


3
该数组已复制,但不是深层副本。这意味着将诸如数字和字符串之类的原始值复制到$ param中,但是对于对象,将复制引用而不是复制对象。$ arr持有对$ date的引用,复制数组$ params也是如此。因此,当您在$ params ['date']上调用函数以更改其值时,您还将更改$ arr ['date']和$ date。当将$ params ['date']设置为字符串时,您只是将$ params对$ date的引用替换为其他内容。
ejegg

1

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

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

0

在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)
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.