美文网首页
PHP引用(&)使用详解(引用计数、写时拷贝)

PHP引用(&)使用详解(引用计数、写时拷贝)

作者: 简简天天 | 来源:发表于2019-12-17 19:00 被阅读0次

一、起因

<?php
$data = ['a','b','c']; 
foreach($data as $k=>$v){
    $v = &$data[$k];
}
print_r($data);

&是引用;修改引用变量的值,那么空间的值也会改变,第一次循环得到$v=&$data[0]=>'a',第二次循环$v=&$data[1]=>'b',可见第一次引用的$data[0]的值已经被改变,所以此时的$data[0]=b,此时$v引用的$data[1],进入第三次循环 此时$v又变为 $v=&$data[2]=>'c',,$v又一次改变,引用的$data[1]的值也被改变为C,所以此时的$data[1]=c,这样循环结束 $data[0]=>'b', $data[1]=>'c', $data[2]=>'c',

二、什么是PHP的引用

php的引用(就是在变量或者函数、对象等前面加上&符号),不同的名字访问同一个变量内容。

1、变量的引用

PHP 的变量引用允许你用两个变量来指向同一个内容

<?php
    $a="ABC";
    $b =&$a;
    echo $a;//这里输出:ABC
    echo $b;//这里输出:ABC
    $b="EFG";
    echo $a;//这里$a的值变为EFG 所以输出EFG
    echo $b;//这里输出EFG

2、函数的引用传递(传址调用)

<?php
    function test(&$a){
        $a=$a+100;
    }
    $b=1;
    echo $b;//输出1
    test($b);   //这里$b传递给函数的其实是$b的变量内容所处的内存地址,通过在函数里改变$a的值 就可以改变$b的值了
    echo "<br>";
    echo $b;//输出101

上面的 test(b); 中的b前面不要加 & 符号,但是在函数“call_user_func_array”中,若要引用传参,就得需要 & 符号,如下代码所示:

<?php
function a(&$b){
    $b++;
}
$c=0;
call_user_func_array('a',array(&$c));
echo $c; //输出 1

3、函数的引用返回

<?php
function &test()
{
    static $b = 0;//声明一个静态变量
    $b = $b + 1;
    echo $b;
    return $b;
}

$a = test();//这条语句会输出 $b的值 为1
$a = 5;
$a = test();//这条语句会输出 $b的值 为2

$a =& test();//这条语句会输出 $b的值 为3
$a = 5;
$a = test();//这条语句会输出 $b的值 为6

$a=test();得到的其实不是函数的引用返回,这跟普通的函数调用没有区别,至于原因:这是php的规定。php规定通过$a=&test(); 方式得到的才是函数的引用返回。
用上面的例子来解释就是$a=test()方式调用函数,只是将函数的值赋给$a而已, 而$a做任何改变,都不会影响到函数中的$b,而通过$a=&test()方式调用函数呢, 他的作用是将return $b中的$b变量的内存地址与$a变量的内存地址指向了同一个地方
即产生了相当于这样的效果($a=&$b;) 所以改变$a的值,也同时改变了$b的值,所以在执行了$a=&test();$a=5;以后,$b的值变为了5
<?php
class talker{
    private $data = 'Hi';
    public function & get(){
        return $this->data;
    }
    public function out(){
        echo $this->data;
    }
}

$aa = new talker();
$d = &$aa->get();
$aa->out(); // Hi
$d = 'How';
$aa->out(); // How
$d = 'Are';
$aa->out(); // Are
$d = 'You';
$aa->out(); // You

4、对象的引用

<?php
class a{
    var $abc="ABC";
}
$b=new a;
$c=$b;
echo $b->abc;//这里输出ABC
echo $c->abc;//这里输出ABC
$b->abc="DEF";
echo $c->abc;//这里输出DEF

以上代码是在PHP5中的运行效果,在PHP5中,对象的赋值是个引用的过程。上列中$b=new a; $c=$b; 其实等效于$b=new a; $c=&$b;
PHP5中默认就是通过引用来调用对象, 但有时你可能想建立一个对象的副本,并希望原来的对象的改变不影响到副本,为了这样的目的,PHP5定义了一个特殊的方法,称为__clone。
<?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( $bar );  // my name is "bar" and I live in "foo".

$baz =& $bar;
print( $baz ); // my name is "bar" and I live in "foo".

$m1 = new MasterOne( $bar );
$m2 = new MasterTwo( $bar );
print( $m1 ); // Master: MasterOne | foo: my name is "bar" and I live in "foo".
print( $m2 ); // Master: MasterTwo | foo: my name is "bar" and I live in "foo".

$bar->setName('baz');
print( $bar ); // my name is "baz" and I live in "foo".
print( $baz ); // my name is "baz" and I live in "foo".
print($m1); // Master: MasterOne | foo: my name is "baz" and I live in "foo".
print($m2); // Master: MasterTwo | foo: my name is "baz" and I live in "foo".

$m2->setFooName( 'MasterTwo\'s Foo' );
print( $m1 ); // Master: MasterOne | foo: my name is "MasterTwo's Foo" and I live in "foo".
print( $m2 ); // Master: MasterTwo | foo: my name is "MasterTwo's Foo" and I live in "foo".

print( $bar ); // my name is "MasterTwo's Foo" and I live in "foo".
print( $baz ); // my name is "MasterTwo's Foo" and I live in "foo".

在php5中,你不需要额外添加什么东西就可到达“对象引用”的功能:
实例对象$m1与$m2中的$bar是对实例$bar的引用,而非拷贝,这是php5中,对象引用的特点,也就是说
1.$m1或$m2内部,任何对$bar的操作都会影响外部对象实例$bar的相关值。
2.外部对象实例$bar的改变也会影响$m1和$m2内部的$bar的引用相关值。

5、引用的作用

如果程序比较大,引用同一个对象的变量比较多,并且希望用完该对象后手工清除它,个人建议用 "&" 方式,然后用$var=null的方式清除. 其它时候还是用php5的默认方式吧. 另外, php5中对于大数组的传递,建议用 "&" 方式, 毕竟节省内存空间使用。

<?php
$a = 1;
$b = & $a;
print($a); // 1
print($b); // 1
$b = null;
print($a); // 空
print($b); // 空
print(null); // 空

6、引用的取消

当你 unset 一个引用,只是断开了变量名和变量内容之间的绑定。这并不意味着变量内容被销毁了。例如: 不会 unset b,只是a。

<?php
$a = 1;
$b = & $a;
print($a); // 1
print($b); // 1
unset($a);
print($a); // Undefined variable: a
print($b); // 1

使用unset(a)与a=null的结果是不一样的。如果该块内存只有a一个映射,那么unset(a)与a=null等价,该内存的引用计数变为0,被自动回收;如果该块内存有a和b两个映射,那么unset(a)将导致a=null且b不变的情况,而a=null会导致a=$b=null的情况。
原因:某变量赋值为null,将导致该变量对应的内存块的引用计数直接置为0,被自动回收。

7、global 引用

当用 global $var 声明一个变量时实际上建立了一个到全局变量的引用。也就是说和这样做是相同的:

<?php
global $var;
$var = 1;
global $var2;
$var2 = 2;
$var1 =& $GLOBALS["var"];
print($var); // 1
print($var1); // 1
print($GLOBALS['var']); // 1
print($GLOBALS['var1']); // 1
print($GLOBALS['var2']); // 2
unset($var1);
print($GLOBALS['var']); // 1
print($GLOBALS['var1']); // Undefined index: var1
print($GLOBALS['var2']); // 2

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

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

function global_references($use_globals){
    global $var1, $var2;
    if (!$use_globals) {
        $var2 =& $var1; // visible only inside the function
        echo $var2; // Example variable
    } 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'

三、PHP应用使用注意事项

1、写时拷贝

很多人误解PHP中的引用跟C当中的指针一样,事实上并非如此,而且很大差别。C语言中的指针除了在数组传递过程中不用显式声明外,其他都需要使用*进行定义,而php中对于地址的指向(类似指针)功能不是由用户自己来实现的,是由Zend核心实现的,php中引用采用的是“引用计数、写时拷贝”的原理,(写时复制(Copy-on-Write,也缩写为COW),顾名思义,就是在写入时才真正复制一份内存进行修改。)

就是除非发生写操作,指向同一个地址的变量或者对象是不会被拷贝的,比如下面的代码:

$a = array('a','c'...'n');
$b = $a;

如果程序仅执行到这里,b和b是相同的,但是并没有像C那样,a和b占用不同的内存空间,而是指向了同一块内存,这就是php和c的差别,并不需要写成b=&a才表示b指向a的内存,zend就已经帮你实现了引用,并且zend会非常智能的帮你去判断什么时候该这样处理,什么时候不该这样处理。

如果在后面继续写如下代码,增加一个函数,通过引用的方式传递参数,并打印输出数组大小。

function printArray(&$arr) //引用传递
{
    print(count($arr));
}
printArray($a);

上面的代码中,我们通过引用把a数组传入printArray()函数,zend引擎会认为printArray()可能会导致对a的改变,此时就会自动为b生产一个a的数据拷贝,重新申请一块内存进行存储。这就是前面提到的“引用计数、写时拷贝”概念。

直观的理解:a将使用自己原始的内存空间,而b,则会使用新开辟的内存空间,而这个空间将使用a的原始(a或者$b改变之前)内容空间的内容的拷贝,然后做对应的改变。

如果我们把上面的代码改成下面这样:

function printArray($arr)  //值传递  
{  
     print(count($arr));  
}  
printArray($a);

上面的代码直接传递$a值到printArray()中,此时并不存在引用传递,所以没有出现写时拷贝。

<?php
$a = array(1,2,3);
$b = $a;
function printArray(&$arr)  //引用传递
{
    $arr[0] = 0;
    print_r($arr);
}
printArray($a);
print_r($a);
print_r($b);
Array
(
    [0] => 0
    [1] => 2
    [2] => 3
)
Array
(
    [0] => 0
    [1] => 2
    [2] => 3
)
Array
(
    [0] => 1
    [1] => 2
    [2] => 3
)
<?php
$a = array(1,2,3);
$b = &$a;
function printArray(&$arr)  //引用传递
{
    $arr[0] = 0;
    print_r($arr);
}
printArray($a);
print_r($a);
print_r($b);
Array
(
    [0] => 0
    [1] => 2
    [2] => 3
)
Array
(
    [0] => 0
    [1] => 2
    [2] => 3
)
Array
(
    [0] => 0
    [1] => 2
    [2] => 3
)
<?php
$a = array(1,2,3);
$b = $a;
function printArray($arr)  //值传递
{
    $arr[0] = 0;
    print_r($arr);
}
printArray($a);
print_r($a);
print_r($b);
Array
(
    [0] => 0
    [1] => 2
    [2] => 3
)
Array
(
    [0] => 1
    [1] => 2
    [2] => 3
)
Array
(
    [0] => 1
    [1] => 2
    [2] => 3
)

2、测试

$a = array(1,2,3);
$b =$a;
function printArray($arr)  //值传递
{
    print(count($arr));
}
$s = microtime(true);
for($i=1;$i<10000;$i++){
    printArray($a);
}
$e = microtime(true);
echo '=----------------------------';
echo $e-$s;//0.038316011428833
<?php
$a = array(1,2,3);
$b = $a;
function printArray(&$arr)  //引用传递
{
    print(count($arr));
}
$s = microtime(true);
for($i=1;$i<10000;$i++){
    printArray($a);
}
$e = microtime(true);
echo '=----------------------------';
echo $e-$s;//0.064435958862305  

结果引用传递性能下降:50%左右。
所以不正确使用引用,性能反而下降。

相关文章

  • PHP引用(&)使用详解(引用计数、写时拷贝)

    一、起因 二、什么是PHP的引用 php的引用(就是在变量或者函数、对象等前面加上&符号),不同的名字访问同一个变...

  • 基于引用计数的内存管理

    引用计数原则 对象的初始引用计数是1。 当引用被创建或者拷贝,引用计数加1。 当对象的引用被销毁或者重新赋值,对象...

  • PHP 的垃圾收集机制是怎样的

    PHP 可以自动进行内存管理,清除不再需要的对象。 PHP 使用了引用计数(reference counting)...

  • Java内存垃圾收集

    如何判断对象可以回收? 引用计数算法记录每个对象被引用的次数,当引用次数为0时,对象不会再被使用。引用计数算法最致...

  • iOS内存管理

    1. 引用计数 1.1 引用计数原理 Objective-C 使用引用计数管理内存。新创建的对象引用计数至少为1,...

  • iOS内存管理1:引用计数

    iOS内存管理1:引用计数 引用计数: Objecttive-C使用引用计数来进行内存管理。然后,引用计数其实是不...

  • copy、mutableCopy

    copy:对于可变对象为深拷贝,引用计数不改变;对于不可变对象是浅拷贝, 引用计数每次加一。始终返回一个不可变对象...

  • iOS的深拷贝和浅拷贝

    1、了解深拷贝和浅拷贝 iOS 里面的浅拷贝: 在 iOS 里面, 使用retain 关键字进行引用计数,就是一种...

  • php高级--PHP的垃圾收集机制是怎样的?

    对象引用计数器机制回收: php 5.3之前使用的垃圾回收机制是单纯的“引用计数”,也就是每个内存对象都分配一个计...

  • PHP垃圾回收机制

    垃圾的产生 之前的文章已经介绍过PHP的引用计数机制-PHP内核探索之变量-理解引用,当变量赋值、传递时并不会直接...

网友评论

      本文标题:PHP引用(&)使用详解(引用计数、写时拷贝)

      本文链接:https://www.haomeiwen.com/subject/uofjnctx.html