293

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

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

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

$b参考$a吗?

4

8 回答 8

309

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

数组赋值总是涉及值复制。使用引用运算符通过引用复制数组。

和给定的例子:

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

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


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

于 2010-01-08T21:34:57.613 回答
146

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

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

仅供参考 - 这被称为“惰性复制”或“写时复制”。

于 2012-03-16T15:56:30.073 回答
95

TL;博士

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

请继续阅读,我的同路人。

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 个!因此,这消耗的内存大约与从非 & 号参数读取一样多。

于 2014-04-08T14:22:46.077 回答
20

默认情况下

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

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

于 2016-04-02T10:52:57.967 回答
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);
于 2010-01-08T21:36:34.147 回答
2

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

<?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)
}
于 2019-05-14T23:18:38.443 回答
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 参数没有 amp,但它仍然会更改 $arr['date'] 的值。这与这里的所有其他解释以及我到目前为止的想法并不真正匹配。

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

于 2014-12-18T21:48:33.927 回答
1

在 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)
}
于 2015-02-01T00:10:15.567 回答