在 PHP 中有两种传参方式,「传值」和「引用」,我们来看看它们的区别。

1. 传值

$foo = 123;
$bar = $foo;
$foo = 456;

echo $bar; // 输出 123

传值很好理解,就是简单的把一个变量的值拷贝一份,然后赋值给另一个变量。变量与变量之间互相独立,改变其中一个变量的值,对其它变量不会产生直接影响。

2. 引用

2.1 介绍

引用在官方手册中的介绍如下:

在 PHP 中引用意味着可以用不同的名字访问同一个变量内容。不像 C 的指针:例如你不能对它们做指针运算,它们也不是真正的内存地址等。PHP 里面,引用是符号表的别名。注意在 PHP 中,变量名和变量内容是不一样的,因此同样的变量内容可以有不同的变量名。最接近的比喻是 Unix 的文件名和文件——变量名是文件路径,而变量内容是文件本身。引用就好比是 Unix 文件系统中的硬链接。

引用有三种基本操作或使用:

  • 引用赋值
  • 引用传递
  • 引用返回

2.2 引用赋值

$foo = 123;
$bar = &$foo;
$foo = 456;

echo $bar; // 输出 456

PHP 的引用允许两个变量指向同一内容。$foo$bar 在这里是完全相同的,并不是 $a 指向了 $b 或者反过来,而是 $a$b 指向了同一个地方。

2.3 引用传递

可以把一个变量通过引用传递给一个函数,这样该函数就可以修改这个变量了。

function foo(&$var)
{
    $var++;
}

$a = 5;
foo($a);
echo $a; // 输出 6

以下内容可以通过引用传递:

  • 变量,如 foo($a)
  • 函数返回的引用,如:
function foo(&$var)
{
    $var++;
}

function &bar()
{
    $a = 5;
    return $a;
}

foo(bar());

除此之外,任何其它表达式都不能通过引用传递,结果将会是未定义。

2.3 引用返回

当你想使用函数来查找引用应该绑定到哪个变量时,引用返回是很有用的。不要用引用返回来提升性能,引擎自己会自动进行优化。仅仅只在有合理的技术原因时才使用引用返回。

class foo
{
    public $value = 42;

    public function &getValue()
    {
        return $this->value;
    }
}

$obj = new foo;
$myValue = &$obj->getValue(); // $myValue 是 $obj->value 的引用,即 42
$obj->value = 2;
echo $myValue; // 输出 2

和参数传递不同,这里必须在两个地方都使用 & ——来表明你希望得到的是一个引用返回,而不是值拷贝。$myValue 是引用绑定,不是普通的赋值。

如果你打算在函数中用 return $this->value 来返回一个引用,是行不通的;因为返回的是一个表达式的结果,而不是一个变量。只能通过函数的引用来返回一个变量,别无他法。

要使用引用返回,必须用引用赋值:

function &collector()
{
    static $collection = array();
    return $collection;
}

$collection = &collector();
$collection[] = 'foo';

要将引用返回传递给另一个要使用的函数:

function &collector()
{
  static $collection = array();
  return $collection;
}

array_push(collector(), 'foo');

注意 array_push(&collector, 'foo'); 会导致一个语法错误。

2.4 对象的引用

对象赋值时,默认是引用传递。

class foo
{
    public $bar = 'baz';
}

$a = new foo;
$b = $a;
echo $a->bar; // 输出 baz
echo $b->bar; // 输出 baz

$b->bar = 'BAZ';
echo $a->bar; // 输出 BAZ
echo $b->bar; // 输出 BAZ

如果想在内存中生成两个一样的对象或者创建一个对象的副本,可以使用 clone

class foo
{
    public $bar = 'baz';
}

$a = new foo;
$b = clone $a;
echo $a->bar; // 输出 baz
echo $b->bar; // 输出 baz

$b->bar = 'BAZ';
echo $a->bar; // 输出 baz
echo $b->bar; // 输出 BAZ