详解 PHP 的引用

PHP不能直接操纵内存,所以是没有指针(内存指针)的概念,但是有引用;

引用是什么

在 PHP 中引用意味着用不同的名字访问同一个变量内容。

这并不像 C 的指针,引用是符号表别名
在PHP中,变量名和变量内容是不一样的,因此同样的内容可以有不同的名字;
最接近的比喻是,Unix 的文件名和文件本身 - - 变量名是目录条目,而变量内容则是文件本身;
引用可以被看做是Unix文件系统中的hardlink(硬链接)。

引用做什么

1、PHP 的引用允许用两个变量来指向同一个内容
(1)用 =& 来把变量的地址传给另一个变量,这样,另一个变量就指向了同一个地址。

// eg.
$a = &$b; // $a 和 $b 指向了同一个变量

$a 和 $b 在这里是完全相同的,并不是 $a 指向了 $b 或者 $b 指向了 $a,而是 $a 和 $b 指向了同一个地址;

(2)如果具有引用的数组被拷贝,其值不会解除引用;对于数组传值给函数也是如此。

// eg.
$a = 123;
$array = array();
$array[1] = &$a;
$abc = $array; // 此时,$abc[1] 仍然引用自 $a,不会解除
$a = 456;
echo $abc[1]; // 显示为 456

(3)如果对一个未定义的变量进行引用赋值、引用参数传递或引用返回,则会自动创建该变量。

// eg.
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)

(4)如果在一个函数内部给一个声明为 global 的变量赋于一个引用,该引用只在函数内部可见;可以通过 $GLOBALS 数组避免这一点。

// eg.
$var1 = 'Example variable';
$var2 = '';
function global_references($use_globals) {
    global $var1, $var2;
    if (!$use_globals) {
        $var2 = &$var1; // 该引用只在函数内部可行
    } else {
        $GLOBALS['var2'] = &$var1; // 该引用适用于全局变量
    }
}
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 只改变了本地变量的引用。

(5)如果在 foreach 语句中给一个具有引用的变量赋值,被引用的对象也被改变。

// eg.
$ref = 0;
$row = &$ref;
foreach(array(1, 2, 3) as $row) {/* do something */}
echo $ref; // 显示为3,值为循环的最后一个数组元素

2、引用传递
通过在函数内建立一个本地变量并且该变量在呼叫范围内引用了同一个内容来实现的。

// eg.
function foo(&$var) {$var++;}
$a = 5;
foo($a);
echo $a; // 显示为6,这是因为 foo 函数中变量 $var 指向了和 $a 指向的同一个内容

3、引用返回
引用返回用在当想用函数找到引用应该被绑定在哪一个变量上面时;
仅在有合理的技术原因时才返回引用!
要返回引用,使用此语法:

class foo {
    public $value = 42;
    public functon &getValue() {
        return $this->value;
    }
}
$obj = new foo;
$myValue = &$obj->getValue(); // 变量 $myValue 引用了对象方法返回 $obj->value
$obj->value = 2;
echo $myValue; // 显示为 2

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

引用不是什么

引用不是指针。
这意味着下面的结构不会产生预期的效果:

$baz = 'hello';
function foo(&$var) {$var = &$GLOBALS['baz'];}
foo($bar);
echo $bar; // 显示为空,不是预期的 hello

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

取消引用

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

eg.
$a = 1;
$b = &$a;
unset($a); // 不会 unset $b,只是 $a

拿这个和 Unix 的 unlink 调用类比一下更有助于理解。

引用定位

许多 PHP 的语法结构是通过引用机制实现的,所以上述有关引用绑定的一切也都适用于这些结构,例如,引用传递 和 引用返回;
其它使用引用的结构有:
1、global 引用
**当用 global $var 声明一个变量时实际上建立了一个到全局变量的引用;
也就是说和这样做是相同的:$var = &$GLOBALS['var'];
所以,unset $var 不会 unset 全局变量。**

2、$this
在一个对象的方法中,$this 永远是调用它的对象的引用;

eg.
$bar =& new fooclass();
$foo =& find_var($bar);

自 PHP5 起,new 自动返回引用,因此使用 =& 已经过时了并且会产生 E_STRICT 级别的消息;
不用 & 运算符会导致对象生成一个拷贝;
如果在类中用 $this,它将作用于该类当前的实例;
没有用 & 的赋值将拷贝这个实例(例如对象)并且 $this 将作用于这个拷贝上,这并不总是想要的结果;
由于性能和内存消耗的问题,通常只想工作在一个实例上面。

3、写时复制(Copy on Write)
普通的 = 号是把一个变量的引用传给另一个变量,如果源变量的内容不变,那么引用它的变量的内容也不会变,即当没有修改变量内容的时候,两个变量是指向同一个地址,但当源变量修改时,引用它的变量就会把源变量的内容复制一份出来放到一个新的内存地址中,引用它的变量则是指向这个新创建的内存地址,这就是:写时复制(Copy on Write);

来看个示例:
如果有 $a, $b = &$a,那么修改 a 时,b 的内容也会随之改变,如果想再用 c 来引用 b,并且在 a 改变时也能影响到 c,那么必须这样写:

$a = 'aa';
$b = &$a;
$c = &$b;

说白了,& 引用符操作的是地址;

如果上面的三行代码最后一行变成: $c = $b; ,那么在 a 发生改变时,输出 c 的值还是 a 原来的值;
为什么呢?分析如下:

a 和 b 指向同一个地址,a 修改了,导致 b 也一样修改,这时,对于 c 来讲,源变量 b 修改了,所以 c 把 b 的内容复制一份出来放到一个新的内存地址,值为原来 b 和 a 指向的变量值,即 aa;

经典示例

讲了辣么多,来几个经典示例瞅瞅:

1、第一个示例

$array = [1, 2, 3];
echo implode(',', $array), "\n";
foreach ($array as &$value) {}    // by reference
echo implode(',', $array), "\n";
foreach ($array as $value) {}     // by value (i.e., copy)
echo implode(',', $array), "\n";

结果:

1,2,3
1,2,3
1,2,2

分析:
第一个循环过后,$value 是数组中最后一个元素的引用;
第二个循环开始:
第一步:复制 $arr[0] 到 $value(注意此时 $value 是 $arr[2] 的引用),$arr[2] 的值变为 $arr[0] 的值,这时数组变成 [1,2,1];
第二步:复制 $arr[1] 到 $value,$arr[2] 的值变为 $arr[1] 的值,这时数组变成 [1,2,2];
第三步:复制 $arr[2] 到 $value,$arr[2] 的值变为 $arr[2] 的值,这时数组变成 [1,2,2];

2、第二个示例

class Config {
    private $values = [];
    public function getValues() {
        return $this->values;
    }
}
$config = new Config();
$config->getValues()['test'] = 'test';
echo $config->getValues()['test'];

结果:
无法输出

分析:
在 PHP 中,除非显示的指定返回引用,否则对于数组 PHP 是值返回,也就是数组的拷贝。因此上面代码对返回数组赋值,实际是对拷贝数组进行赋值,非原数组赋值。
如果把代码改成:
public function &getValues()...
那么就可以输出了;

要点:
PHP 中对于对象,默认是引用返回,数组和内置基本类型默认均按值返回;
这个要与其它语言区别开来(很多语言对于数组是引用传递);


文章地址:http://www.meegle.cn/2016/01/20/php-references/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值