一篇搞懂 PHP 的引用

image

引用变量的解释

  1. 引用是什么?
    再PHP中引用意味着用不同的名字访问同一个变量的内容;
    注意: 在PHP中,变量名和变量内容是不一样的,因此同样的内容可以有不同的名字。最接近的比喻是`Unix` 的 `文件名` 和 `文件本身` -- 变量名是目录条目,而变量内容则是文件本身,引用可以被看做是`Unix`文件系统的 `硬链接` ;
    example1.php

    <?php
    $var = "foo";
    $ref1 =& $var; // 引用 $var 的内存空间
    //安装debug扩展,可以使用此方法打印
    xdebug_debug_zval('var');//(refcount=2, is_ref=1)string 'foo' (length=3)
    
    echo $ref1; // >Notice:  Undefined variable: ref1
    echo $var; // >foo
    ?>
  2. 引用不是什么?
    引用不是指针 , 这意味着一下的结构不会产生预期的效果
    <?php
    function foo(&$var)
    {
    $var = &$GLOBALS["baz"];
    }
    foo($bar);
    ?>

    这将使 foo 函数中的 $var 变量在函数调用时和 $bar 绑定在一起,但接着又被重新绑定到了 $GLOBALS["baz"] 上面。不可能通过引用机制将 $bar 在函数调用范围内绑定到别的变量上面,因为在函数 foo 中并没有变量 $bar(它被表示为 $var,但是 $var 只有变量内容而没有调用符号表中的名字到值的绑定)。

example2.php

<?php
$ref = [1,2,3];
$c = count($ref);
$foo = ['A'];

for($i=0;$i<$c;$i++)
    $foo[] =& $ref[$i];

print_r($foo);
print_r($ref);

$ref = [4,5,6];

print_r($foo);
print_r($ref);
?>
/* 打印结果:
Array
(
    [0] => A
    [1] => 1
    [2] => 2
    [3] => 3
)
Array
(
    [0] => 1
    [1] => 2
    [2] => 3
)
Array
(
    [0] => A
    [1] => 1
    [2] => 2
    [3] => 3
)
Array
(
    [0] => 4
    [1] => 5
    [2] => 6
)
*/

注意example2.php$ref 经历了 Copy-on-Write 缩写为 COW 写时复制

  1. 引用做什么?
    PHP的引用允许用两个变量来指向同一个内容
    example3.php
    <?php
    $a =& $b;
    ?>

    这意味着 $a$b 指向了同一个变量。
    注意:
    a> $a$b 在这里是完全相同的,这并不是 $a 指向了 $b 或者相反,而是 $a$b 指向了同一个地方。
    b> 如果具有引用的数组被拷贝,其值不会解除引用。对于数组传值给函数也是如此。
    c> 如果对一个未定义的变量进行引用赋值、引用参数传递或引用返回,则会自动创建该变量。

example4.php

<?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)
?>

d>同样的语法可以用在函数中,它返回引用,以及用在 new 运算符中:

example4.php

?>
$bar = &new fooclass();
$fll = &find_var($bar);
?>

自 PHP 5 起,自动返回引用,因此在此使用 =& 已经过时了并且会产生 E_STRICT 级别的消息。
e> 不用 & 运算符导致对象生成了一个拷贝。如果在类中用 $this,它将作用于该类当前的实例。
没有用 & 的赋值将拷贝这个实例(例如对象)并且 $this 将作用于这个拷贝上,这并不总是想要的结果。
由于性能和内存消耗的问题,通常只想工作在一个实例上面。
尽管可以用 @ 运算符来抑制构造函数中的任何错误信息,例如用 @new,但用 &new 语句时这不起效果。这是 Zend 引擎的一个限制并且会导致一个解析错误。
f> 注意:如果在一个函数内部给一个声明为 global 的变量赋于一个引用,该引用只在函数内部可见。可以通过使用 $GLOBALS 数组避免这一点。

example5.php

<?php
$var1 = "Example variable";
$var2 = "";

function global_references($use_globals)
{
    global $var1, $var2;
    if (!$use_globals) {
        $var2 =& $var1; // visible only inside the function
    } else {
        $GLOBALS["var2"] =& $var1; // visible also in global context
    }
}

global_references(false);
echo "var2 is set to '$var2'\n"; // var2 is set to ''
global_references(true);
echo "var2 is set to '$var2'\n"; // var2 is set to 'Example variable'
?>

global $var; 当成是 $var =& $GLOBALS['var']; 的简写。从而将其它引用赋给 $var 只改变了本地变量的引用。

example6.php

<?php
$ref = 0;
$row =& $ref;
foreach (array(1, 2, 3) as $row) {
    // do something
}
echo $ref; // 3  数组中的最后一个元素
?>
  1. 引用的其他用途:
    引用做的第二件事是用引用传递变量。这是通过在函数内建立一个本地变量并且该变量在呼叫范围内引用了同一个内容来实现的

example7.php

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

$a=5;
foo($a);
?>

将使 $a 变成 6。这是因为在 foo 函数中变量 $var 指向了和 $a 指向的同一个内容。更多详细解释见引用传递

  1. 引用传递
    可以将一个变量通过引用传递给函数,这样该函数就可以修改其参数的值;

example8.php

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

$a=5;
foo($a);
// $a is 6 here
?>

注意:
在函数调用时没有引用符号——只有 函数定义 中有。光是函数定义就足够使参数通过引用来正确传递了。在最近版本的 PHP 中如果把 & 用在 foo(&$a); 中会得到一条警告说"Call-time pass-by-reference"已经过时了。
以下内容可以通过引用传递:
1> 变量,例如 foo($a)
2> New 语句,例如 foo(new foobar())
3> 从函数中返回的引用;

example9.php

//以下内容可以通过引用传递:
<?php

error_reporting(E_ALL);
function foo(&$var)
{
    $var++;
    echo $var;
}

function &bar()
{
    $a = 5;
    return $a;
}
foo(bar());
?>
  1. 引用返回
    引用返回用在当想用函数找到引用应该被绑定在哪一个变量上面时。不要用返回引用来增加性能,引擎足够聪明来自己进行优化。仅在有合理的技术原因时才返回引用!

example10.php

<?php
class foo {
    public $value = 42;

    public function &getValue() {
        return $this->value;
    }
}

$obj = new foo;
$myValue = &$obj->getValue(); //42
$obj->value = 2;
echo $myValue;                // 打印 $obj->value,  2.
?>

本例中 getValue 函数所返回的对象的属性将被赋值,而不是拷贝,就和没有用引用语法一样。

注意:
参数传递 不同,这里必须在 两个地方 都用 & 符号—— 指出返回的是一个引用,而不是通常的一个拷贝, 同样也指出 $myValue 是作为引用的绑定,而不是通常的赋值。

如果试图这样从函数返回引用:return ($this->value); ,这将 不会起作用因为在试图返回一个表达式的结果而不是一个引用的变量
只能从函数返回引用变量 ——没别的方法。
如果代码试图返回一个动态表达式或 new 运算符的结果,自 PHP 4.4.0 和 PHP 5.1.0 起会发出一条 E_NOTICE 错误。

6.取消引用
当 unset 一个引用,只是断开了变量名和变量内容之间的绑定。这并不意味着变量内容被销毁了。

<?php
$a = 1;
$b =& $a;
unset($a);
?>

不会 unset $b,只是 $a
再拿这个和 Unixunlink 调用来类比一下可能有助于理解。

7.引用定位
许多 PHP 的语法结构是通过引用机制实现的,所以上述有关引用绑定的一切也都适用于这些结构。一些结构,例如引用传递和返回,已经在上面提到了。

其他的引用结构有:
1> global :当用 global $var 声明一个变量时实际上建立了一个到全局变量的引用。

<?php
$var =& $GLOBALS["var"];
?>

这意味着,例如,unset $var 不会 unset 全局变量。
2> $this :在一个对象的方法中,$this 永远是调用它的对象的引用。

以上就是引用的全部内容;

相关题目:

<?php
 //每次循环的结果是什么?最终程序执行完的结果是什么?为什么?
$data = ['a', 'b', 'c'];
foreach ($data as $key => $val){
    $val = &$data[$key];
    var_dump($data);
}
var_dump($data);
?>
php
本作品采用《CC 协议》,转载必须注明作者和本文链接
周小帅
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 8

欢迎纠错和补充!!

5年前 评论
$a = 1;
$b =& $a;
unset($a);
echo $b;//1

function foo(&$var) { 
    // $var++;
    unset($var);
}
$a=1;
foo($a);//如同$a=&$var;
echo $a;//1

$data = ['a', 'b', 'c'];
foreach ($data as $key => $val){
    $val = &$data[$key];
    print_r($data);
}
print_r($data);
Array
(
    [0] => a
    [1] => b
    [2] => c
)
Array
(
    [0] => b
    [1] => b
    [2] => c
)
Array
(
    [0] => b
    [1] => c
    [2] => c
)
Array
(
    [0] => b
    [1] => c
    [2] => c
)
5年前 评论

@monkey example9.php 里边foo(bar()) 这样用感觉会报错,我在本地运行的时候也会报错.是我用错了么。求大佬指点

5年前 评论

@坐忘 把代码和错误信息贴出来看下(和php版本),我在本地运行了一下,这里没有报错。

5年前 评论

@monkey php 版本是 7.1的 就是感觉foo() 函数未定义

file

file

5年前 评论

@坐忘 是这样的,确实会报函数未定义的错误,可以结合 example8.php 中的 foo();

error_reporting(E_ALL);
function foo(&$var)
{
    $var++;
    echo $var;
}

function &bar()
{
    $a = 5;
    return $a;
}
foo(bar());

这样就不会报错了;
本文中example9.php 中的例子只是想说明

在函数调用时没有引用符号——只有 函数定义 中有。光是函数定义就足够使参数通过引用来正确传递了。

所有给你带来的误解,不好意思哈!
我马上修改 exmaple9.php 代码,感谢提醒!

5年前 评论

@monkey 嗯嗯,我也感觉需要联系上下文:smile: :smile: :smile: 。这篇文章还是学到了很多,谢谢大佬分享。

5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!