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 教程。