← PHP array_walk函数详解:遍历数组并施加自定义回调 PHP数组是什么?从底层结构到项目实战的完整讲解 →

PHP字符串引用是什么?能解决哪些实际问题

著
原创 2026-10-09 PHP 已有人查阅

一、先分清两个概念:变量引用与字符串“引用”

PHP里的“引用”(Reference)本质是变量符号表别名,不是C语言那种指针,也不是Java的对象引用。用&符号让两个变量名指向同一个内部数据(zval)。改一个,另一个跟着变。

那“字符串引用”是什么?它并不是说字符串本身变成引用类型。字符串在PHP里是标量,标量默认按值传递。所谓字符串引用,通常指下面几种情况:

  • 用&把两个变量绑成别名,其中一个存的是字符串;

  • 函数参数用&$str接收,函数内部修改会影响外部;

  • 数组元素用&$arr['key']取引用,批量处理字符串;

  • 对象属性、静态属性上的字符串引用;

  • 引用计数与写时复制(Copy-On-Write)对字符串性能的影响。

这几个场景合起来,才是日常说的“PHP字符串引用”。

二、变量引用:字符串也能被“绑”在一起

看一段基础代码:

<?php
$a = '代码号学习编程';
$b = &$a;

$b = '代码号进阶';

echo $a; // 输出:代码号进阶

$b=&$a;之后,$a和$b是同一个变量的两个名字。给$b赋新字符串,$a读出来也变了。这里要注意:引用绑定发生在赋值那一刻,不是先复制再绑定。

很多教程只讲“引用能改原值”,但没说清楚一个坑:如果先$b=$a;再想$b=&$a;,第一次赋值已经产生了一次值拷贝。字符串短的时候无所谓,字符串很大时,这次拷贝就是白开销。

个人经验:在模板引擎或配置合并里,经常需要让多个键共享同一个字符串配置。用引用可以避免重复内存,但一定要在赋值前就绑定,顺序错了就退化成值拷贝。

三、函数传参:&$str的真实用途

函数参数加&,是字符串引用最常见的落地点。

<?php
function appendSuffix(&$str, $suffix) {
    $str .= $suffix;
}

$title = '代码号';
appendSuffix($title, '学习编程');
echo $title; // 代码号学习编程

如果不加&,函数内部的$str是副本,外部$title不变。加&后,函数直接改外部变量。

但这里有个设计建议:不要为了“省内存”而滥用引用传参。PHP7以后,标量传参本身有写时复制,只读场景下不复制。只有确实需要“函数修改外部字符串”时才用&。如果只是拼接返回,直接return$str.$suffix;更清晰。

项目反思:早期写过一个日志格式化函数,参数用了&$message,结果调用方传了字面量formatLog(&'abc'),直接报错。引用参数只能接变量,不能接表达式或字面量。这个坑在代码评审时被指出,后来改成返回值方式,调用方更自由。

四、数组元素引用:批量处理字符串的利器

数组里存字符串时,可以用引用遍历,直接改原数组。

<?php
$langs = ['php', 'java', 'python'];

foreach ($langs as &$lang) {
    $lang = strtoupper($lang);
}
unset($lang); // 必须断开引用

print_r($langs);
// Array ( [0] => PHP [1] => JAVA [2] => PYTHON )

这段代码的关键在unset($lang)。foreach结束后,$lang仍然是之后一个元素的引用。如果后面再写$lang='go';,数组之后一个元素会被意外改掉。这个坑非常经典,几乎每个PHP开发者都踩过。

另一种写法是用键名:

<?php
$langs = ['php', 'java', 'python'];

foreach ($langs as $key => $value) {
    $langs[$key] = strtoupper($value);
}

这种写法没有引用残留问题,但多了一次键名查找。数据量小的时候,两种写法差异可以忽略;数据量大、循环体复杂时,引用遍历少一次哈希查找,但风险也更高。我的建议是:优先用键名写法,除非性能测试确实证明引用遍历有显著收益。

五、引用计数与写时复制:字符串引用的底层视角

PHP的变量存储在zval里,字符串是zend_string。多个变量指向同一个zend_string时,引用计数(refcount)增加。当某个变量要修改内容,且refcount>1,才真正复制一份,这就是写时复制(Copy-On-Write)。

<?php
$a = str_repeat('代码号', 1000);
$b = $a;        // refcount +1,没有复制内容
$b .= '学习';   // refcount > 1,触发复制

理解这一点后,就能明白:普通赋值并不总是立即复制大字符串。PHP已经帮你做了优化。真正需要手动引用的场景,是你要让“修改一个变量影响另一个变量”,而不是单纯为了省内存。

六、对象属性与静态属性上的字符串引用

对象的属性也可以被引用:

<?php
class Config {
    public static $siteName = '代码号';
}

$alias = &Config::$siteName;
$alias = '代码号学习编程';
echo Config::$siteName; // 代码号学习编程

静态属性引用在全局配置场景里偶尔有用,但可读性差。更推荐用常量或配置数组,而不是让一个外部变量偷偷改类内部状态。

七、哪些场景不建议用字符串引用

  1. 只读传参:不需要修改外部变量时,不要加&。

  2. 返回引用:function&getStr()这种写法容易让调用方困惑,除非做迭代器或单例池。

  3. 字面量传参:引用参数不能接'abc'这种字面量。

  4. foreach后忘记unset:引用残留会污染后续逻辑。

  5. 用引用做“性能优化”:PHP7+的COW已经够用,手动引用反而增加维护成本。

八、一个综合示例:配置覆盖

<?php
$baseConfig = [
    'title' => '代码号',
    'desc'  => '学习编程',
];

$override = &$baseConfig['title'];
$override = '代码号学习编程';

print_r($baseConfig);
// Array ( [title] => 代码号学习编程 [desc] => 学习编程 )

这个例子在项目里可以用于“默认配置+环境覆盖”的场景。但更工程化的做法是用array_replace_recursive,而不是靠引用去改。引用在这里能跑通,但可读性和可测试性都不如数组合并函数。

PHP字符串引用的核心不是“字符串特殊”,而是“变量别名机制作用在字符串上”。它能做四件事:让两个变量共享同一份字符串数据、让函数修改外部字符串、让数组元素被原地批量修改、让对象或静态属性被外部别名修改。用之前先问一句:我是要“改外部变量”,还是只想“读值”?如果是后者,引用就是多余的。把unset和字面量限制记牢,能避开大部分坑。

← PHP array_walk函数详解:遍历数组并施加自定义回调 PHP数组是什么?从底层结构到项目实战的完整讲解 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号