[L1] PHP 中引用传递与值传递的区别
一句话结论
值传递复制值、各自独立;引用传递共享同一变量,修改互相可见。
体系讲解
原理:两种传递方式的语义
- 值传递(pass by value):函数接收的是实参的副本,函数内修改形参不影响外部变量。PHP 中标量(int / float / string / bool)和数组默认都是值传递。
- 引用传递(pass by reference):函数接收的是实参的别名(用
&声明),形参和实参指向同一个变量,函数内修改直接反映到外部。
机制:PHP 的 Copy-On-Write(COW)优化
PHP 并不会在值传递时立即复制数据。赋值或传参时,新旧变量先共享同一块内存,仅当其中一方发生写操作时才真正复制——这就是写时复制(COW)。因此值传递在大多数场景下并不比引用传递慢。
引用传递(&)则会破坏 COW:一旦变量被标记为引用,任何赋值都不再触发复制,而是直接修改共享数据。
对象的特殊情况
PHP 5+ 中对象变量存储的是一个对象标识符(object identifier),传参时复制的是这个标识符,而非对象本身。因此函数内修改对象属性,外部可见——但这不是引用传递,而是标识符的值传递。区别在于:函数内对变量重新赋值($obj = new Other())不会影响外部。
结论:对开发的直接影响
- 绝大多数场景使用默认的值传递即可,COW 保证了性能。
- 引用传递适用于需要函数"就地修改"调用方变量的场景(如
preg_match()的$matches参数)。 - 滥用引用传递会让数据流难以追踪,增加调试难度。
考察意图
- 验证候选人是否理解 PHP 参数传递的默认行为
- 考察对"对象是引用传递"这一常见误区的认知
- 判断候选人是否了解 COW 机制,能解释"值传递不一定慢"
追问链
PHP 中对象是引用传递吗?
简答:不是。对象变量传递的是对象标识符的副本(值传递)。函数内修改对象属性会影响外部(因为指向同一个对象),但对变量本身重新赋值不会影响外部。只有显式加
&才是真正的引用传递。什么是 Copy-On-Write?它对值传递的性能有什么影响?
简答:COW 是 PHP 的内存优化策略。赋值或传参时不立即复制,而是共享内存,直到某一方写操作时才真正复制。因此值传递一个大数组但不修改它,内存开销几乎为零。
foreach中使用引用&$value后不unset会产生什么问题?简答:循环结束后
$value仍然是数组最后一个元素的引用。如果后续代码再次对$value赋值,会意外修改数组的最后一个元素。正确做法是循环结束后立即unset($value)断开引用。函数签名中
function foo(array &$arr)和function foo(array $arr)在实际使用中应如何选择?简答:需要就地修改调用方数组时用
&(如往数组中追加元素)。否则用值传递——COW 保证性能,且不会产生副作用。现代 PHP 更推荐用返回值传递结果,而非引用参数。
易错点
以为"PHP 对象是引用传递":这是最广泛的误区。对象变量存的是标识符,传参复制的是标识符。在函数内
$obj = null不影响外部,但$obj->name = 'x'影响外部。能否区分这两点是关键。foreach引用变量未unset导致数据污染:这是真实项目中极高频的 bug。循环结束后引用变量仍绑定数组最后一个元素,后续任何对该变量的赋值都会"莫名其妙"改掉数组末尾。以为值传递大数组性能差:由于 COW,值传递一个百万元素的数组但不修改它,内存和时间开销极小。只有在函数内写操作时才触发复制。盲目用引用传递反而可能破坏 COW 导致不必要的复制。
代码示例
<?php
// 1. 值传递 vs 引用传递
function addOne(int $n): void {
$n += 1;
}
function addOneRef(int &$n): void {
$n += 1;
}
$x = 10;
addOne($x);
echo $x; // 10 — 值传递,外部不变
addOneRef($x);
echo $x; // 11 — 引用传递,外部被修改
// 2. 对象传递的真相
class User {
public function __construct(public string $name) {}
}
function rename(User $user): void {
$user->name = '李四'; // 修改属性 → 外部可见
}
function replace(User $user): void {
$user = new User('王五'); // 重新赋值 → 外部不可见
}
$u = new User('张三');
rename($u);
echo $u->name; // 李四
replace($u);
echo $u->name; // 仍然是李四,不是王五
// 3. foreach 引用陷阱
$arr = [1, 2, 3];
foreach ($arr as &$value) {
$value *= 2;
}
// 此时 $value 仍是 $arr[2] 的引用
$value = 99; // 意外修改了 $arr[2]!
print_r($arr); // [2, 4, 99] — 期望是 [2, 4, 6]
// 正确做法:循环后断开引用
$arr2 = [1, 2, 3];
foreach ($arr2 as &$v) {
$v *= 2;
}
unset($v); // ← 关键:断开引用
$v = 99;
print_r($arr2); // [2, 4, 6] — 正确