引用究竟做什么

使用引用有三种基本操作:按引用赋值按引用传递按引用返回。本节将介绍这些操作,并提供进一步阅读的链接。

按引用赋值

在这些操作中,PHP 引用允许你让两个变量引用相同的内容。也就是说,当你做

<?php
$a
=& $b;
?>
这意味着 $a$b 指向相同的内容。

注意:

$a$b 在这里完全相等。 $a 并没有指向 $b,反之亦然。 $a$b 指向同一个位置。

注意:

如果你按引用赋值、传递或返回一个未定义的变量,它将被创建。

示例 #1 使用未定义变量的引用

<?php
function foo(&$var) { }

foo($a); // $a 被“创建”并赋值为 null

$b = array();
foo($b['b']);
var_dump(array_key_exists('b', $b)); // bool(true)

$c = new stdClass;
foo($c->d);
var_dump(property_exists($c, 'd')); // bool(true)
?>

相同的语法可以用于返回引用的函数

<?php
$foo
=& find_var($bar);
?>

对不返回引用的函数使用相同的语法会产生错误,使用 new 运算符的结果也会产生错误。尽管对象作为指针传递,但这些指针与引用不同,如 对象和引用 中所述。

警告

如果你将一个引用赋值给一个在函数内部声明为 global 的变量,该引用只能在函数内部可见。你可以通过使用 $GLOBALS 数组来避免这种情况。

示例 #2 在函数内部引用全局变量

<?php
$var1
= "示例变量";
$var2 = "";

function
global_references($use_globals)
{
global
$var1, $var2;
if (!
$use_globals) {
$var2 =& $var1; // 仅在函数内部可见
} else {
$GLOBALS["var2"] =& $var1; // 在全局上下文中也可见
}
}

global_references(false);
echo
"var2 赋值为 '$var2'\n"; // var2 赋值为 ''
global_references(true);
echo
"var2 赋值为 '$var2'\n"; // var2 赋值为 '示例变量'
?>
global $var; 视为 $var =& $GLOBALS['var']; 的快捷方式。因此,将另一个引用赋值给 $var 仅会更改局部变量的引用。

注意:

如果你在 foreach 语句中将值赋值给带有引用的变量,引用也会被修改。

示例 #3 引用和 foreach 语句

<?php
$ref
= 0;
$row =& $ref;
foreach (array(
1, 2, 3) as $row) {
// 做一些事情
}
echo
$ref; // 3 - 迭代数组的最后一个元素
?>

虽然不完全是按引用赋值,但使用语言结构 array() 创建的表达式也可以通过在要添加的数组元素前加上 & 来表现得像引用一样。示例

<?php
$a
= 1;
$b = array(2, 3);
$arr = array(&$a, &$b[0], &$b[1]);
$arr[0]++; $arr[1]++; $arr[2]++;
/* $a == 2, $b == array(3, 4); */
?>

然而,请注意,数组中的引用可能很危险。对带有右边的引用的变量进行正常(非按引用)赋值不会将左侧变成引用,但数组中的引用在这些正常赋值中会保留。这同样适用于函数调用,其中数组按值传递。示例

<?php
/* 标量变量的赋值 */
$a = 1;
$b =& $a;
$c = $b;
$c = 7; //$c 不是引用;对 $a 或 $b 不会产生任何变化

/* 数组变量的赋值 */
$arr = array(1);
$a =& $arr[0]; //$a 和 $arr[0] 在同一个引用集中
$arr2 = $arr; //不是按引用赋值!
$arr2[0]++;
/* $a == 2, $arr == array(2) */
/* 即使不是引用,$arr 的内容也会发生变化! */
?>
换句话说,数组的引用行为是在元素级定义的;单个元素的引用行为与数组容器的引用状态无关。

按引用传递

引用做的第二件事是按引用传递变量。这是通过在函数中创建一个局部变量,以及在调用范围内的变量引用相同的内存空间来完成的。示例

<?php
function foo(&$var)
{
$var++;
}

$a=5;
foo($a);
?>
将使 $a 成为 6。这是因为在函数 foo 中,变量 $var 引用了与 $a 相同的内容。有关此的更多信息,请阅读 按引用传递 部分。

按引用返回值

引用可以做的第三件事是 按引用返回值

添加注释

用户贡献注释 23 个注释

ladoo at gmx dot at
19 年前
我在使用 pbaltz at NO_SPAM dot cs dot NO_SPAM dot wisc dot edu 下方示例的扩展版本时遇到了问题。
这可能有点令人困惑,尽管如果你仔细阅读了手册,它就非常清楚了。它使引用始终指向变量内容的事实非常清楚(至少对我来说是这样)。

<?php
$a
= 1;
$c = 2;
$b =& $a; // $b 指向 1
$a =& $c; // $a 现在指向 2,但 $b 仍然指向 1;
echo $a, " ", $b;
// 输出:2 1
?>
dexant9t at gmail dot com
3 年前
重要的是你是在处理引用还是值

这里我们处理的是值,因此在引用上操作也会更新原始变量;

$a = 1;
$c = 22;

$b = & $a;
echo "$a, $b"; // 输出:1, 1

$b++;
echo "$a, $b";// 输出:2, 2 两个值都更新了

$b = 10;
echo "$a, $b";// 输出:10, 10 两个值都更新了

$b =$c; // 这将值 2 赋值给 $b,这也会更新 $a
echo "$a, $b";// 输出:22, 22

但是,如果你没有使用 $b=$c 而是
$b = &$c; // 只有 $b 的值被更新,$a 仍然指向 10,$b 现在是变量 $c 的引用

echo "$a, $b"// 输出:10, 22
Hlavac
16 年前
注意这一点

foreach ($somearray as &$i) {
// 更新一些 $i...
}
...
foreach ($somearray as $i) {
// $somearray 的最后一个元素被神秘地覆盖了!
}

问题是 $i 在第一个 foreach 之后包含对 $somearray 的最后一个元素的引用,第二个 foreach 很乐意对其进行赋值!
elrah [] polyptych [dot] com
13 年前
看来引用可能会有副作用。以下是两个示例。两者都只是将一个数组复制到另一个数组。在第二个示例中,在复制之前对第一个数组中的一个值进行了引用。在第一个示例中,索引 0 处的值指向两个不同的内存位置。在第二个示例中,索引 0 处的值指向相同的内存位置。

我不会说这是一个错误,因为我不知道 PHP 的设计行为是什么,但我认为任何开发人员都不会期望这种行为,所以请注意。

如果在脚本中进行数组复制并期望一种行为,但随后在脚本中更早地添加对数组中值的引用,然后发现数组复制行为意外地发生了变化,那么这可能会导致问题。

<?php
// 示例一
$arr1 = array(1);
echo
"\nbefore:\n";
echo
"\$arr1[0] == {$arr1[0]}\n";
$arr2 = $arr1;
$arr2[0]++;
echo
"\nafter:\n";
echo
"\$arr1[0] == {$arr1[0]}\n";
echo
"\$arr2[0] == {$arr2[0]}\n";

// 示例二
$arr3 = array(1);
$a =& $arr3[0];
echo
"\nbefore:\n";
echo
"\$a == $a\n";
echo
"\$arr3[0] == {$arr3[0]}\n";
$arr4 = $arr3;
$arr4[0]++;
echo
"\nafter:\n";
echo
"\$a == $a\n";
echo
"\$arr3[0] == {$arr3[0]}\n";
echo
"\$arr4[0] == {$arr4[0]}\n";
?>
amp at gmx dot info
17 年前
乍一看可能并不明显的东西
如果你想使用引用遍历数组,你不能使用简单的值赋值 foreach 控制结构。你必须使用扩展的键值赋值 foreach 或 for 控制结构。

简单的值赋值 foreach 控制结构会生成对象或值的副本。以下代码

$v1=0;
$arrV=array(&$v1,&$v1);
foreach ($arrV as $v)
{
$v1++;
echo $v."\n";
}

会产生

0
1

这意味着 foreach 中的 $v 不是对 $v1 的引用,而是数组中实际元素所引用的对象的副本。

代码

$v1=0;
$arrV=array(&$v1,&$v1);
foreach ($arrV as $k=>$v)
{
$v1++;
echo $arrV[$k]."\n";
}



$v1=0;
$arrV=array(&$v1,&$v1);
$c=count($arrV);
for ($i=0; $i<$c;$i++)
{
$v1++;
echo $arrV[$i]."\n";
}

都会产生

1
2

因此遍历了原始对象(都是 $v1),这正是我们所期望的。

(在 php 4.1.3 上测试)
Anonymous
8 年前
回复 ' elrah [] polyptych [dot] com ',要记住的一点是,数组(或类似的大型数据持有者)默认情况下是按引用传递的。因此,这种行为不是副作用。并且对于数组复制和在函数中传递数组总是通过 '按引用传递' 来完成的...
nay at woodcraftsrus dot com
13 年前
在 PHP 中,如果你想在程序中共享一个对象,你实际上不再需要指针了

<?php
class foo{
protected
$name;
function
__construct($str){
$this->name = $str;
}
function
__toString(){
return
'my name is "'. $this->name .'" and I live in "' . __CLASS__ . '".' . "\n";
}
function
setName($str){
$this->name = $str;
}
}

class
MasterOne{
protected
$foo;
function
__construct($f){
$this->foo = $f;
}
function
__toString(){
return
'Master: ' . __CLASS__ . ' | foo: ' . $this->foo . "\n";
}
function
setFooName($str){
$this->foo->setName( $str );
}
}

class
MasterTwo{
protected
$foo;
function
__construct($f){
$this->foo = $f;
}
function
__toString(){
return
'Master: ' . __CLASS__ . ' | foo: ' . $this->foo . "\n";
}
function
setFooName($str){
$this->foo->setName( $str );
}
}

$bar = new foo('bar');

print(
"\n");
print(
"Only Created \$bar and printing \$bar\n");
print(
$bar );

print(
"\n");
print(
"Now \$baz is referenced to \$bar and printing \$bar and \$baz\n");
$baz =& $bar;
print(
$bar );

print(
"\n");
print(
"Now Creating MasterOne and Two and passing \$bar to both constructors\n");
$m1 = new MasterOne( $bar );
$m2 = new MasterTwo( $bar );
print(
$m1 );
print(
$m2 );

print(
"\n");
print(
"Now changing value of \$bar and printing \$bar and \$baz\n");
$bar->setName('baz');
print(
$bar );
print(
$baz );

print(
"\n");
print(
"Now printing again MasterOne and Two\n");
print(
$m1 );
print(
$m2 );

print(
"\n");
print(
"Now changing MasterTwo's foo name and printing again MasterOne and Two\n");
$m2->setFooName( 'MasterTwo\'s Foo' );
print(
$m1 );
print(
$m2 );

print(
"Also printing \$bar and \$baz\n");
print(
$bar );
print(
$baz );
?>
charles at org oo dot com
16 年前
指向下面我的帖子。
当你在循环中使用引用时,你需要使用 unset($var)。

例如
<?php
foreach($var as &$value)
{
...
}
unset(
$value);
?>
Oddant
11 年前
关于数组引用的示例。
我认为这应该在数组章节中写。
实际上,如果你在某种程度上是编程语言的初学者,你应该注意数组是指向字节向量的一组指针。

<?php $arr = array(1); ?>
$arr 这里包含一个指向数组所在位置的引用。

<?php echo $arr[0]; ?>
对数组进行解引用以访问它的第一个元素。

现在,你应该注意的一点是(即使你不是编程语言的初学者),PHP 使用引用来包含数组的不同值。 这是有道理的,因为 PHP 数组元素的类型可以不同。

考虑以下示例

<?php

$arr
= array(1, 'test');

$point_to_test =& $arr[1];

$new_ref = 'new';

$arr[1] =& $new_ref;

echo
$arr[1]; // echo 'new';
echo $point_to_test; // echo 'test' ! (仍然指向内存中的某个地方)

?>
php.devel at homelinkcs dot com
19 年前
回复 lars at riisgaardribe dot dk,

当一个变量被复制时,在副本被修改之前,内部使用引用。 因此,在你所处的情况下,你不应该使用引用,因为它不会节省任何内存使用,而且会增加逻辑错误的可能性,正如你所发现的。
dovbysh at gmail dot com
17 年前
对帖子 "php at hood dot id dot au 04-Mar-2007 10:56" 的解决方案

<?php
$a1
= array('a'=>'a');
$a2 = array('a'=>'b');

foreach (
$a1 as $k=>&$v)
$v = 'x';

echo
$a1['a']; // 将输出 x

unset($GLOBALS['v']);

foreach (
$a2 as $k=>$v)
{}

echo
$a1['a']; // 将输出 x

?>
Amaroq
14 年前
我认为我的上一篇文章需要进行修正。

当存在构造函数时,我上一篇文章中提到的奇怪行为不会发生。 我的猜测是 PHP 将 reftest() 视为构造函数(可能是因为它是第一个函数?),并在实例化时运行它。

<?php
class reftest
{
public
$a = 1;
public
$c = 1;

public function
__construct()
{
return
0;
}

public function
reftest()
{
$b =& $this->a;
$b++;
}

public function
reftest2()
{
$d =& $this->c;
$d++;
}
}

$reference = new reftest();

$reference->reftest();
$reference->reftest2();

echo
$reference->a; // 输出 2.
echo $reference->c; // 输出 2.
?>
akinaslan at gmail dot com
13 年前
在这个例子中,类名与其第一个函数不同,但没有构造函数。最后,正如你猜到的那样,"a" 和 "c" 是相等的。所以,如果同时没有构造函数,并且类名和第一个函数名相同,"a" 和 "c" 永远不会相等。我认为,只要函数名不同,PHP 不会寻找任何构造函数。

<?php
class reftest_new
{
public
$a = 1;
public
$c = 1;

public function
reftest()
{
$b =& $this->a;
$b++;
}

public function
reftest2()
{
$d =& $this->c;
$d++;
}
}

$reference = new reftest_new();

$reference->reftest();
$reference->reftest2();

echo
$reference->a; // 输出 2.
echo $reference->c; // 输出 2.
?>
Amaroq
16 年前
引用变量的顺序很重要。

<?php
$a1
= "One";
$a2 = "Two";
$b1 = "Three";
$b2 = "Four";

$b1 =& $a1;
$a2 =& $b2;

echo
$a1; // 输出 "One"
echo $b1; // 输出 "One"

echo $a2; // 输出 "Four"
echo $b2; // 输出 "Four"
?>
Drewseph
16 年前
如果你在将变量传递给接受引用变量的函数之前设置它,那么在函数内部编辑变量会变得非常困难(如果不是不可能的话)。

示例
<?php
function foo(&$bar) {
$bar = "hello\n";
}

foo($unset);
echo(
$unset);
foo($set = "set\n");
echo(
$set);

?>

输出
hello
set

这让我很困惑,但事实就是这样。
dnhuff at acm dot org
16 年前
回复 Drewseph 使用 foo($a = 'set'); 其中 $a 是一个引用形式参数。

$a = 'set' 是一个表达式。表达式不能通过引用传递,你难道不喜欢这样吗?我确实不喜欢。如果你打开 E_NOTICE 的错误报告,你会得到关于它的提示。

解决方案: $a = 'set'; foo($a); 这样可以实现你想要的结果。
firespade at gmail dot com
17 年前
这里有一个关于引用的很好的小例子。这是我最容易理解的方式,希望它能帮助其他人。

$b = 2;
$a =& $b;
$c = $a;
echo $c;

// 然后... $c = 2
Amaroq
14 年前
在类中使用引用时,可以引用 $this-> 变量。

<?php
class reftest
{
public
$a = 1;
public
$c = 1;

public function
reftest()
{
$b =& $this->a;
$b = 2;
}

public function
reftest2()
{
$d =& $this->c;
$d++;
}
}

$reference = new reftest();

$reference->reftest();
$reference->reftest2();

echo
$reference->a; // 输出 2.
echo $reference->c; // 输出 2.
?>

然而,这似乎并不完全可靠。在某些情况下,它可能会表现得很奇怪。

<?php
class reftest
{
public
$a = 1;
public
$c = 1;

public function
reftest()
{
$b =& $this->a;
$b++;
}

public function
reftest2()
{
$d =& $this->c;
$d++;
}
}

$reference = new reftest();

$reference->reftest();
$reference->reftest2();

echo
$reference->a; // 输出 3.
echo $reference->c; // 输出 2.
?>

在这个第二个代码块中,我已经修改了 reftest(),使 $b 增加而不是直接更改为 2。不知何故,它最终等于 3 而不是 2,应该等于 2。
php at hood dot id dot au
17 年前
我今天在 foreach 中使用引用时发现了一些东西

<?php
$a1
= array('a'=>'a');
$a2 = array('a'=>'b');

foreach (
$a1 as $k=>&$v)
$v = 'x';

echo
$a1['a']; // 输出 x

foreach ($a2 as $k=>$v)
{}

echo
$a1['a']; // 输出 b (!)
?>

在阅读手册后,这看起来是预期的行为。但这让我困惑了好几天!

(我使用的解决方案是将第二个 foreach 也变成引用)
strata_ranger at hotmail dot com
14 年前
引用的一种有趣但冷门的用法:创建具有任意维度的数组。

例如,一个函数可以接收数据库中的结果集并生成一个多维数组,根据一个(或多个)列进行索引。这在以下情况下可能很有用:你希望结果集以层次结构的方式访问,或者你只是希望结果集根据每一行主键/唯一键字段的值进行索引。

<?php
function array_key_by($data, $keys, $dupl = false)
/*
* $data - 要进行键值映射的多维数组
* $keys - 包含要使用的索引/键的列表。
* $dupl - 处理包含相同值的行的方式。TRUE 将其存储为数组,FALSE 覆盖之前的行。
*
* 返回一个由 $keys 索引的多维数组,如果出错则返回 NULL。
* 维数等于提供的 $keys 数量(如果 $dupl=TRUE 则加 1)。
*/
{
// 健壮性检查
if (!is_array($data)) return null;

// 允许将单个键作为标量传递
if (is_string($keys) or is_integer($keys)) $keys = Array($keys);
elseif (!
is_array($keys)) return null;

// 我们的输出数组
$out = Array();

// 遍历输入 $data 的每一行
foreach($data as $cx => $row) if (is_array($row))
{

// 遍历我们的 $keys
foreach($keys as $key)
{
$value = $row[$key];

if (!isset(
$last)) // 仅第一个 $key
{
if (!isset(
$out[$value])) $out[$value] = Array();
$last =& $out; // 将 $last 绑定到 $out
}
else
// 第二个和后续的 $key....
{
if (!isset(
$last[$value])) $last[$value] = Array();
}

// 将 $last 绑定到更深一层的一维。
// 第一轮:是 &$out,现在是 &$out[...]
// 第二轮:是 &$out[...],现在是 &$out[...][...]
// 第三轮:是 &$out[...][...],现在是 &$out[...][...][...]
// (等等)
$last =& $last[$value];
}

if (isset(
$last))
{
// 在这一点上,将 $row 复制到我们的输出数组中
if ($dupl) $last[$cx] = $row; // 保留之前的
else $last = $row; // 覆盖之前的
}
unset(
$last); // 打破引用
}
else return
NULL;

// 完成
return $out;
}

// 用于测试函数的示例结果集
$data = Array(Array('name' => 'row 1', 'foo' => 'foo_a', 'bar' => 'bar_a', 'baz' => 'baz_a'),
Array(
'name' => 'row 2', 'foo' => 'foo_a', 'bar' => 'bar_a', 'baz' => 'baz_b'),
Array(
'name' => 'row 3', 'foo' => 'foo_a', 'bar' => 'bar_b', 'baz' => 'baz_c'),
Array(
'name' => 'row 4', 'foo' => 'foo_b', 'bar' => 'bar_c', 'baz' => 'baz_d')
);

// 首先,让我们根据一列进行键值映射(结果:二维数组)
print_r(array_key_by($data, 'baz'));

// 或者,根据两列进行键值映射(结果:三维数组)
print_r(array_key_by($data, Array('baz', 'bar')));

// 我们也可以根据三列进行键值映射(结果:四维数组)
print_r(array_key_by($data, Array('baz', 'bar', 'foo')));

?>
joachim at lous dot org
21 年前
所以,要创建一个按引用设置函数,您需要在参数列表 _和_ 赋值中指定引用语义,如下所示

class foo{
var $bar;
function setBar(&$newBar){
$this->bar =& newBar;
}
}

忘记任何两个“&”符号,在调用 setBar 后,$foo->bar 将最终成为一个副本。
butshuti at smartrwanda dot org
11 年前
这似乎是隐藏的行为:当类函数与类同名时,它似乎在创建类的对象时被隐式调用。
例如,您可以查看函数“reftest()”的命名:它位于类“reftest”中。该行为可以通过以下方式测试

<?php
class reftest
{
public
$a = 1;
public
$c = 1;

public function
reftest1()
{
$b =& $this->a;
$b++;
}

public function
reftest2()
{
$d =& $this->c;
$d++;
}

public function
reftest()
{
echo
"REFTEST() called here!\n";
}
}

$reference = new reftest();
/*请注意,上面的代码也会隐式调用 reference->reftest()*/

$reference->reftest1();
$reference->reftest2();

echo
$reference->a."\n"; // 回显 2,而不是 3,正如之前注意到的。
echo $reference->c."\n"; // 回显 2。
?>

上面的输出

REFTEST() called here!
2
2

请注意,reftest() 似乎被调用了(虽然没有明确调用它)!
admin at torntech dot com
10 年前
到目前为止还没有讨论过的是对引用的引用。
我需要一种快速而肮脏的方法来为错误的命名创建别名,直到进行适当的重写。
希望这能为其他人节省测试时间,因为它没有在 Does/Are/Are Not 页面中介绍。
远非最佳实践,但它确实有效。

<?php
$a
= 0;

$b =& $a;
$a =& $b;

$a = 5;
echo
$a . ', ' . $b;
//输出: 5,5

echo ' | ';

$b = 6;
echo
$a . ',' . $b;
//输出: 6,6

echo ' | ';
unset(
$a );
echo
$a . ', ' . $b;

//输出: , 6

class Product {

public
$id;
private
$productid;

public function
__construct( $id = null ) {
$this->id =& $this->productid;
$this->productid =& $this->id;
$this->id = $id;
}

public function
getProductId() {
return
$this->productid;
}

}

echo
' | ';

$Product = new Product( 1 );
echo
$Product->id . ', ' . $Product->getProductId();
//输出 1, 1
$Product->id = 2;
echo
' | ';
echo
$Product->id . ', ' . $Product->getProductId();
//输出 2, 2
$Product->id = null;
echo
' | ';
echo
$Product->id . ', ' . $Product->getProductId();
//输出 ,
To Top