重新分配引用数组的变量会重新分配原始变量

时间:2019-05-08 15:22:15

标签: php

我有此代码:

$originalBar = [
  'baz' => 18
];

function modify (&$bar) {
  $bar['test'] = true;
  $bar = [
    'data' => 42
  ];

  global $originalBar;
  echo 'same ' . ($bar === $originalBar) . PHP_EOL;
}

modify($originalBar);
var_dump($originalBar);

我知道,由于该函数通过引用接受参数,因此任何传递的数组都将被修改。所以我希望通过以下方式更改原始数组:

$bar['test'] = true;

...并将test的键$originalBar设置为true。但是,当我重新分配$bar时,我希望变量不再指向原始数组($originalBar),并且以后的任何更改都不会对其进行修改。显然,情况并非如此,因为输出为:

same 1
array(1) {
  ["data"]=>
  int(42)
}

通过重新分配$bar,我也重新分配了$originalBar。我希望它的功能与JavaScript中的功能相同,这就是为什么我一开始很困惑的原因。

我的问题是-是否在某处记录了该文件?我阅读了Passing by Reference文档,但在那没找到。

编辑:如果不是在 modify 函数中执行此操作:

$bar = [
  'data' => 42
];

...我这样做:

$arr = [
  'data' => 42
];

$bar = &$arr;

...我得到了最初的预期结果:

same 
array(2) {
  ["baz"]=>
  int(18)
  ["test"]=>
  bool(true)
}

有趣的是,在这种情况下,$originalBar未分配给$arr的值,而是保留其旧值。

3 个答案:

答案 0 :(得分:2)

我想纠正对问题,评论和自我回答的误解。

    在PHP中传递
  • 对象的行为与在JS中传递对象相似。这不是通过引用传递,而是通过值传递“对象指针”。差异是细微但重要的。通过引用将变量传递给函数时,可以在函数内部为该变量分配任何值,并且该变量也将在函数外部具有该值。传递对象并没有赋予您这种能力-您不能将其替换为其他对象或值42-但可以使您变异对象-您可以调用方法或设置属性,并且函数外的代码将能够看到结果。
  • 此处JS和PHP的区别在于JS中的数组是对象,而在PHP中则不是,因此按值传递时它们不具有这种“可变对象指针”行为。
  • 用PHP和许多其他语言实现的
  • 通过引用传递,意味着对函数内部变量的任何赋值或修改都反映在函数外部。这里的数组没有什么特别的,$foo=42;$foo++;也会发生同样的事情。
  • JS没有显式传递引用的等效项,但是其他语言(例如C#和Pascal)具有相同的概念,但语法不同。

当您通过引用分配($foo =& $bar)时,PHP中的情况变得更加不常见;想到它的一种方法是,您有一个变量,并为其指定了两个名称。

我更喜欢将您写成$bar = &$newValue的内容拼写为$bar =& $newValue,因为该操作并不是真正的“为$bar分配内容”,而是“绑定名称$bar作为某物的别名”。在绑定该别名时,它将丢弃该名称的所有先前绑定,因此它将撤消该名称的“引用传递”性质。

答案 1 :(得分:2)

一个可能的混乱点是,PHP中的数组与JS中的数组/对象不同,出于传递值的目的,它们的行为类似于字符串或数字。

在PHP中,按值传递的数组在弄脏函数时将为copied on write,就像字符串或数字类型一样:

function modify($a) {
    global $foo;
    var_dump($a === $foo); # => true

    $a['hello'] = "world";
    var_dump($a === $foo); # => false, we wrote to $a and it was copied.
}

$foo = ["baz" => 42];    
modify($foo);
var_dump($foo); # => ["baz" => 42] (the original was unchanged after the function call)

从JS的角度来看,我们可能希望$a['hello'] = "world";会反映在外部对象上而不导致创建副本:

const modify = a => {
  console.log(a === foo); // => true
  a.hello = "world";
  console.log(a === foo); // => true
};

const foo = {bar: "baz"};
modify(foo);
console.log(foo); // => {"bar": "baz", "hello": "world"}

PHP中的按值传递行为不足为奇:

class A {
    function __construct() {
        $this->bar = "hello";
    }
}

function modify($a) {
    global $foo;
    var_dump($a === $foo); # => true

    $a->bar = "world";
    var_dump($a === $foo); # => true
}

$foo = new A();
modify($foo);
var_dump($foo); /* => object(A)#1 (1) {
                        ["bar"]=>
                        string(5) "world"
                      } 
                */

在PHP中,通过引用传递可对原始数组进行突变:

function modify(&$a) {
    global $foo;
    var_dump($a === $foo); # => true

    $a['hello'] = "world";
    var_dump($a === $foo); # => true
}

$foo = ["baz" => 42];    
modify($foo);
print_r($foo); # => ["baz" => 42, "hello" => "world"] 

参考变量也可以重新分配给新值:

function modify(&$a) {
    global $foo;
    var_dump($a === $foo); # => true

    $a = "world";
    var_dump($a === $foo); # => true
}

$foo = ["baz" => 42];    
modify($foo);
print_r($foo); # => "world"

由于JS不支持通过引用进行传递,因此除了使用引用运算符来支持函数内数组的JS /类对象变异之外,JS和PHP在这些行为上没有明显的平行之处。

答案 2 :(得分:1)

因为它是预期的行为,所以未在任何地方进行记录。我只是看错了。 PHP文档说:

  

您可以通过引用将变量传递给函数,以便该函数可以修改变量

我有一个错误的印象,因为对象默认是在JavaScript中通过引用传递的,在那里,重新分配参数变量不会更改原始变量。

在PHP中通过引用传递的目的是能够修改变量,而不仅仅是其指向的对象。这意味着,您不仅可以更改对象,还可以重新分配传递的变量。

正如@ggorlen在对我的问题的评论中所说:

  

引用运算符实际上在做两件事:1)公开原始内存位置以进行重新分配; 2)允许对诸如数组之类的复杂结构进行突变。在JS中,第一个永远不可能,而第二个永远都是可能的。在这些方面,PHP提供了一些“灵活性”,一方面,function ($var)比大多数lang更具限制性,而function (&$var)则比大多数语言更宽松,这并不完全直观。