上一节我们讲了按值传递,它的核心是“函数拿副本,原值不受影响”。这是一种安全的隔离机制。但有些场景下,你恰恰需要函数去修改外部原始变量——比如交换两个变量的值、给数组批量加前缀、或者一次性从函数返回多个计算结果。这时候,按引用传递(Call by Reference) 就该上场了。
按引用传递的核心规则:函数接收的不是变量值的副本,而是变量本身在内存中的地址。 函数内部对这个参数的任何修改,直接作用于外部的原始变量。这个机制打破了函数内外的数据隔离,赋予了函数直接操控外部变量的能力。
语法与基础示例
让一个参数变成按引用传递很简单:在函数定义时,给参数名前面加一个&符号。
function 函数名(&$参数名) {
// 对$参数名的修改会直接影响外部原始变量
}
调用时语法和普通函数一样,不需要额外加任何符号。&只出现在定义里。
基础示例:字符串追加
<?php
function appendText(&$str) {
$str .= "——追加的文本内容";
}
$myText = "原始内容";
appendText($myText);
echo $myText;
?>
输出:原始内容——追加的文本内容
$myText在调用appendText()之后实实在在地变了。因为&$str拿到的是$myText的引用,函数内部的.=拼接操作直接作用在原始变量上。这和按值传递形成鲜明对比——如果去掉那个&,$myText依然是"原始内容",函数内部的修改只影响副本。
多个实例深入理解
实例1:数值递增
<?php
function increase(&$num) {
$num++;
}
$count = 10;
increase($count);
echo "递增后的值:{$count}";
?>
输出:递增后的值:11
按值传递需要把递增后的值return出来再重新赋值。按引用传递省掉了这一步,$count直接被函数内部操作改了。
实例2:交换两个变量的值
这是一个很能体现按引用传递优势的经典场景。没有引用的话,交换函数无法工作。
<?php
function swapValues(&$a, &$b) {
$temp = $a;
$a = $b;
$b = $temp;
}
$x = 5;
$y = 10;
echo "交换前:x = {$x},y = {$y}<br/>";
swapValues($x, $y);
echo "交换后:x = {$x},y = {$y}";
?>
输出:
交换前:x = 5,y = 10
交换后:x = 10,y = 5
函数需要同时修改两个外部变量。只有按引用传递能做到这一点——函数直接操作$x和$y的内存地址,把它们的值互换。
实例3:批量修改数组元素
处理数组时,按引用传递能让你把数组传给函数,函数直接在原数组上做修改,而不是返回一个新数组。
<?php
function addPrefix(&$arr, $prefix) {
// 注意:这里$value前面也要加&,否则只改副本
foreach ($arr as &$value) {
$value = $prefix . $value;
}
}
$fruitList = ['苹果', '香蕉', '樱桃'];
addPrefix($fruitList, '新鲜');
print_r($fruitList);
?>
输出:
Array
(
[0] => 新鲜苹果
[1] => 新鲜香蕉
[2] => 新鲜樱桃
)
这里有两个&需要注意:一个是函数参数定义时的&$arr,让函数拿到数组本身的引用;另一个是foreach里的&$value,让循环体内部能直接修改数组的每个元素。两个缺一不可。
实例4:函数返回引用
按引用的思路不仅能用“进”的方向(传参),还能用到“出”的方向(返回值)。一个函数可以返回对某个变量的引用,调用方拿到引用后可以直接修改原始数据。
<?php
// 注意函数名前也有&,表示这个函数返回的是引用
function &getElement(&$arr, $key) {
return $arr[$key];
}
$userData = ['name' => 'Alice', 'age' => 25];
// 用=&接收引用,而不是复制值
$nameRef =& getElement($userData, 'name');
$nameRef = 'Bob';
echo $userData['name']; // 输出Bob,原始数组被修改了
?>
输出:Bob
getElement()返回的不是$userData['name']的值"Alice",而是这个元素本身的内存引用。$nameRef =&拿到这个引用后,给$nameRef赋新值,等于直接修改了$userData数组里的对应元素。
本节课程知识要点
按引用传递是一个有权限的工具,使用时有几个关键点需要把握:
-
&写在函数定义里,调用时不加。这点容易搞混。定义时function foo(&$param),调用时就是foo($var),不会写成foo(&$var)。 -
按引用传递绕过了作用域隔离。函数可以直接修改外部变量,这是一种能力,也是一份责任。用得好很便捷,用过头会让数据流变得难以追踪。
-
不是所有变量都能按引用传递。比如你不能把一个字面量(如
foo(&5))传给引用参数,5没有内存地址。只能传变量。 -
foreach里修改数组元素也要加&。这是另一个常见的&使用场景,和函数参数引用是同一个原理。 -
取消引用用
unset()。如果foreach里用了&$value,循环结束后记得unset($value),否则后续对$value的误操作可能会意外修改数组的之后一个元素。
按引用传递的实用价值
1. 优化大数据的处理性能
按值传递在遇到大型数组或对象时,会复制整个数据结构,消耗内存和时间。按引用传递只传一个内存地址,避免了复制开销。
<?php
function processLargeData(&$data) {
// 直接在原数据上操作,无需复制
$data['processed'] = true;
}
$largeArray = range(1, 100000); // 十万个元素的数组
processLargeData($largeArray);
?>
$largeArray有十万个元素,如果按值传递,PHP要在内存里再复制十万个元素。按引用传递只传一个指针,这在处理大量数据时有可见的性能差异。
2. 支持链式调用
在面向对象编程里,方法返回$this的引用,可以实现链式调用,让代码更紧凑。
<?php
class Counter {
private $count = 0;
public function &inc() {
$this->count++;
return $this;
}
public function getCount() {
return $this->count;
}
}
$counter = new Counter();
// 链式调用:连续三次递增
$counter->inc()->inc()->inc();
echo "计数结果:" . $counter->getCount();
?>
输出:计数结果:3
inc()返回$this的引用,所以可以紧接着继续调用inc(),一行代码完成多次递增。
3. 从函数返回多个值
PHP函数本身只能return一个值。如果想返回多个计算结果,一种方案是把用于接收结果的变量以引用方式传入函数,让函数把结果“写”进这些变量。
<?php
function calculate($a, $b, &$sum, &$product) {
$sum = $a + $b;
$product = $a * $b;
}
$num1 = 4;
$num2 = 5;
calculate($num1, $num2, $total, $multiplyResult);
echo "和:{$total},积:{$multiplyResult}";
?>
输出:和:9,积:20
$total和$multiplyResult在调用前是未初始化的,函数内部通过引用直接给它们赋了值。调用结束后,外部就有了两个计算结果。
什么时候用按引用传递,什么时候不用
主观建议: 在代码号学习编程的过程中,按引用传递是一个功能比较强的工具,但用之前值得想一想。如果按值传递配合return就能解决问题,我倾向于用那种方式——数据流是单向的,容易追踪。在以下几种情况下,按引用传递是更自然的选择:
-
需要函数直接修改传入的数组或大对象,而且调用方的意图就是“把这个数据交给函数处理,处理完它就得变”。
-
需要一次调用返回多个值,且用数组封装返回值会让调用方代码变丑。
-
操作大型数据集为了性能考虑,避免不必要的复制。
如果不属于以上场景,按值传递是更稳妥的默认选项。按引用传递打破了函数内外的边界,边界一旦模糊,调试时就要多留一个心眼。
按引用传递和按值传递不是对立的,而是互补的。理解了两者的区别和适用场景,你就能在写函数时做出有依据的取舍——什么时候让函数安分守己地只依赖参数和返回值,什么时候给它直接操作外部变量的权限。这个判断力的形成,需要时间的积累。但方向对了,积累起来就快。