PHP 值复制 vs 引用
最后修改于 2025 年 5 月 16 日
本教程解释了 PHP 如何处理值复制和引用,这会影响变量赋值和参数传递。 理解这些概念对于编写高效的 PHP 代码至关重要,尤其是在处理大型数据结构时。
PHP 中的值复制
默认情况下,PHP 对变量赋值使用写时复制语义。 这意味着变量按值复制,但实际的复制只发生在修改变量时。
- 简单类型 (
int
,float
,string
,bool
) 总是按值复制 - 复杂类型 (
array
,object
,resource
) 对值复制使用写时复制 - 可以使用
&
运算符显式创建引用
特征 | 按值复制 | 按引用复制 |
---|---|---|
默认行为 | 是 (写时复制) | 否 (必须使用 & 运算符) |
内存使用 | 直到修改时有效 | 共享,直到取消设置 |
示例 | $b = $a |
$b = &$a |
按值复制示例
PHP 的默认复制行为会在修改变量时创建独立的副本,但使用写时复制来优化内存使用。
<?php declare(strict_types=1); // Simple types are always copied by value $a = 10; $b = $a; // Copy by value echo "Original: a = $a, b = $b\n"; $b = 20; // Doesn't affect $a echo "After change: a = $a, b = $b\n"; // Arrays use copy-on-write $arr1 = [1, 2, 3]; $arr2 = $arr1; // Copy-on-write (no actual copy yet) $arr2[0] = 99; // Now makes a copy echo "arr1[0] = {$arr1[0]}, arr2[0] = {$arr2[0]}\n";
在示例中,修改 $b
不会影响 $a
,因为它们是独立的副本。 但是,修改 $arr2
会创建一个新的数组副本,而 $arr1
保持不变。 这是由于 PHP 的写时复制优化,它避免了在未进行修改之前不必要的内存使用。
$ php main.php Original: a = 10, b = 10 After change: a = 10, b = 20 arr1[0] = 1, arr2[0] = 99
PHP 中的字符串:按值复制
PHP 中的字符串是标量类型,始终按值复制。 将一个字符串变量分配给另一个变量会创建一个独立的副本。 修改一个字符串不会影响另一个字符串,因为每个变量都保存其自己的值。
<?php declare(strict_types=1); $s1 = "hello"; $s2 = $s1; // Copy by value echo "Original: s1 = $s1, s2 = $s2\n"; $s2 = "there"; // s1 is not affected echo "After change: s1 = $s1, s2 = $s2\n";
在此示例中,$s1
和 $s2
是独立的。 更改 $s2
不会影响 $s1
,因为字符串在 PHP 中始终按值复制。
$ php main.php Original: s1 = hello, s2 = hello After change: s1 = hello, s2 = there
对象和引用
自 PHP 5 以来,默认情况下,对象按引用处理。 将对象分配给一个新变量不会创建副本,而是创建对同一对象的另一个引用。
<?php declare(strict_types=1); class Person { public $name; public function __construct($name) { $this->name = $name; } } $person1 = new Person('Alice'); $person2 = $person1; // Both reference the same object echo "Original: person1->name = {$person1->name}, "; echo "person2->name = {$person2->name}\n"; $person2->name = 'Bob'; // Affects both references echo "After change: person1->name = {$person1->name}, "; echo "person2->name = {$person2->name}\n"; // To create a true copy, use clone $person3 = clone $person1; $person3->name = 'Charlie'; echo "After clone: person1->name = {$person1->name}, "; echo "person3->name = {$person3->name}\n";
在示例中,修改 $person2
也会影响 $person1
,因为它们引用同一个对象。 但是,使用 clone
会创建一个新对象,允许独立修改。
$ php main.php Original: person1->name = Alice, person2->name = Alice After change: person1->name = Bob, person2->name = Bob After clone: person1->name = Bob, person3->name = Charlie
显式引用
PHP 允许使用 &
运算符创建显式引用。 这会创建一个别名,其中两个变量都指向相同的数据。
<?php declare(strict_types=1); $a = 10; $b = &$a; // $b is now a reference to $a echo "Original: a = $a, b = $b\n"; $b = 20; // Changes both $a and $b echo "After change: a = $a, b = $b\n"; // Works with arrays too $arr1 = [1, 2, 3]; $arr2 = &$arr1; $arr2[0] = 99; echo "arr1[0] = {$arr1[0]}, arr2[0] = {$arr2[0]}\n"; // To break a reference unset($b); // Removes the reference, $a keeps its value
在此示例中,修改 $b
也会影响 $a
,因为它们是对相同值的引用。 这也适用于数组。
$ php main.php Original: a = 10, b = 10 After change: a = 20, b = 20 arr1[0] = 99, arr2[0] = 99
参数传递
PHP 支持通过值传递和通过引用传递函数参数。 默认情况下,参数通过值传递,但可以指定引用。
<?php declare(strict_types=1); // Pass by value (default) function modifyValue($x) { $x = 100; echo "Inside function: x = $x\n"; } $a = 10; modifyValue($a); echo "After function: a = $a\n"; // Pass by reference function modifyReference(&$x) { $x = 100; echo "Inside function: x = $x\n"; } $b = 10; modifyReference($b); echo "After function: b = $b\n"; // Objects are always passed by reference function modifyObject($obj) { $obj->value = 100; } $object = new stdClass(); $object->value = 10; modifyObject($object); echo "Object value: {$object->value}\n";
在示例中,第一个函数修改了变量的副本,而第二个函数修改了原始变量。 对象按引用传递,因此其属性被直接修改。
$ php main.php Inside function: x = 100 After function: a = 10 Inside function: x = 100 After function: b = 100 Object value: 100
返回引用
PHP 函数可以返回引用,但应谨慎使用,因为它可能导致代码混淆和维护问题。
<?php declare(strict_types=1); class Container { private $data = []; public function &getDataReference() { return $this->data; } public function getDataCopy() { return $this->data; } } $container = new Container(); $ref = &$container->getDataReference(); $ref['key'] = 'value'; // Modifies the container's private data $copy = $container->getDataCopy(); $copy['key'] = 'new value'; // Doesn't affect container var_dump($container->getDataCopy());
在此示例中,getDataReference
方法返回对容器私有数据的引用,允许修改影响原始数据。 getDataCopy
方法返回一个副本,因此修改不会影响原始数据。
$ php main.php array(1) { ["key"]=> string(5) "value" }
总结与最佳实践
- PHP 使用写时复制来实现高效的值复制
- 自 PHP 5 以来,对象通过引用处理
- 使用
&
运算符进行显式引用 - 函数参数默认通过值传递
- 使用
clone
创建独立的对象副本 - 避免在绝对必要时返回引用
- 理解这些行为以编写内存高效的 PHP 代码
本教程涵盖了 PHP 对值和引用复制的方法,包括赋值行为、参数传递和对象处理。
来源
作者
列出 所有 PHP 教程。