设为首页 收藏本站
查看: 738|回复: 0

[经验分享] PHP中引用的详解(引用计数、写时拷贝)

[复制链接]

尚未签到

发表于 2015-8-26 14:49:53 | 显示全部楼层 |阅读模式
  PHP中引用的详解   huangguisu
  《PHP5中文手册》内容中"引用的解释"一文的摘要:
  

1. PHP中引用的特性

PHP中引用意味着用不同的名字访问同一个变量内容,引用不是C的指针(C语言中的指针里面存储的是变量的内容,在内存中存放的地址),是变量的另外一个别名或者映射。注意在
PHP 中,变量名和变量内容是不一样的,因此同样的内容可以有不同的名字。最接近的比喻是 Unix 的文件名和文件本身――变量名是目录条目,而变量内容则是文件本身。引用可以被看作是 Unix 文件系统中的紧密连接或者wins的快捷方式。


2.变量的引用


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

<?php
$a = &quot; ABC &quot; ;
$b   =& $a ;
echo   $a ; // 这里输出:ABC
echo   $b ; // 这里输出:ABC
$b = &quot; EFG &quot; ;
echo   $a ; // 这里$a的值变为EFG 所以输出EFG
echo   $b ; // 这里输出EFG
?>这意味着 $a 和 $b 指向了同一个变量。


注: $a 和$b
在这里是完全相同的,这并不是$a 指向了$b
或者相反,而是$a 和$b
指向了同一个地方。
3 对象的引用(对象的赋&#20540;传递)



在PHP5中两者映射到同一对象,不需要使用引用符。




<?php
class fooclass{
public $att ;
}
$a = new fooclass();
$b = $a;
$a->att = 1;
$b->att = 2;
echo 'a obj:',($a->att),'<br>';
echo 'b obj:',($b->att);
/**
* 结果:
* a obj:2
b obj:2
*/

  
在PHP5中 对象的赋&#20540;是个引用的过程。上列中
  


$a = new fooclass();
$b = $a;
  
  
  
  
  
  
  
其实等效于$b=new fooclass(); $c=&$b;

PHP5中默认就是通过引用来调用对象, 但有时你可能想建立一个对象的副本,并希望原来的对象的改变不影响到副本 . 为了这样的目的,PHP5定义了一个特殊的方法,称为__clone。
  
自 PHP 5 起,new 自动返回引用,因此在此使用 =& 已经过时了并且会产生 E_STRICT 级别的消息。


  
在php4中,对象的赋&#20540;是个拷贝过程,
  
$a = new foo fooclass(), 实际上$a和new fooclass()的映射到不同对象实例,其中new
fooclass产生的是一个匿名的fooclass对象实例 所以需要显式地使用$a = & new fooclass()来进行引用赋&#20540;传递。
  
所以在php4中,为了节省内存空间,$b=new fooclass()一般会改成引用的模式,即 $b=& new fooclass()。



4.函数的引用传递(传址调用)


引用传递的定义必须在函数定义中体现,在函数使用中,不要写成foo(&$a)的样子,否则会报&quot;Call-time
pass-by-reference过时&quot;的警报。



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



但是在函数“call_user_func_array”中,若要引用传参,就得需要 & 符号,如下代码所示:

function  test( & $b ){
$b ++ ;
}
$c = 0 ;
call_user_func_array ( 'test' , array ( & $c ));
echo   $c ;
5 函数的引用返回


代码:

<? 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();得到的其实不是函数的引用返回,这跟普通的函数调用没有区别,只是将函数的&#20540;赋给$a而已, 而$a做任何改变 都不会影响到函数中的$b。

而通过$a=&test()方式调用函数呢, 他的作用是 将return $b中的 $b变量的内存地址与$a变量的内存地址 指向了同一个地方

即产生了相当于这样的效果($a=&$b;) 所以改变$a的&#20540; 也同时改变了$b的&#20540; 所以在执行了

$a=&test();

$a=5;


以后,$b的&#20540;变为了5。


注意:

和参数传递不同,这里必须在两个地方都用 & 符号:定义和使用时都需要加&符号。

& test()指出返回的是一个引用,而不是通常的一个拷贝。

$a =& test()同样也指出 $a 是作为引用的绑定,而不是通常的赋&#20540;。
  

这里是为了让大家理解函数的引用返回才使用静态变量的,其实函数的引用返回多用在对象中:

<?php
/**
* This is the way how we  use  pointer to access variable inside the  class .
*/
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();
$d   =   ' How ' ;
$aa -> out();
$d   =   ' Are ' ;
$aa -> out();
$d   =   ' You ' ;
$aa -> out();
//the output is  &quot; Hi How Are You &quot;


6 global 引用


当用 global $var 声明一个变量时实际上建立了一个到全局变量$_GLOBAL的引用。具体可以写成如下语句:




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


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



如果全局变量赋&#20540;给另外另外一个变量$var_test,删除这个$var_test,也不会 unset 全局变量

<?php
global $var;
$var = &quot;Example variable&quot;;
$var_test =  &  $GLOBALS [ 'var' ];
echo '$var_test is set to ',$var_test,'<br>'; //
$var_test = 11;
unset($var_test);
echo '$GLOBALS[var] is set to ',$GLOBALS ['var'],'<br>';
/**
* 输出结果:
$var_test is set to Example variable
$GLOBALS[var] is set to 11
*/






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





$var1 = &quot;Example variable&quot;;
$var2 = &quot;&quot;;
function global_references($use_globals) {
global $var1, $var2;
if (!$use_globals) {
$var2 =& $var1; // visible only inside the function
} else {
$GLOBALS[&quot;var2&quot;] =& $var1; // visible also in global context
}
}
global_references(false);
echo &quot;var2 is set to '$var2'&quot;, '<br>'; // var2 is set to ''
global_references(true);
echo &quot;var2 is set to '$var2'&quot;; // var2 is set to 'Example variable'


$var2 =& $var1; // visible only inside the function

那么实际上就是改变了$var2的映射关系,之前与$_GLOBAL['var2']的映射就断开了,再也无法修改函数外部$var2的内容。此特性也同样作用在用引用传递函数参数的例子中。

另外,需要注意在函数中unset($var2)不会真正释放变量内容,可以看做只是断开了函数内部$var2的映射而已。



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


<?php
$a   =   1 ;
$b   =&   $a ;
unset  ( $a );
echo $b; //输出:1:


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

原因:某变量赋&#20540;为null,将导致该变量对应的内存块的引用计数直接置为0,被自动回收。



8 . $this

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



9. PHP引用使用注意事项
很多人误解Php中的引用跟C当中的指针一样,事实上并非如此,而且很大差别。C语言中的指针除了在数组传递过程中不用显式申明外,其他都需要使用*进行定义,而php中对于地址的指向(类&#20284;指针)功能不是由用户自己来实现的,是由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&#20540;到printArray()中,此时并不存在引用传递,所以没有出现写时拷贝。
测试:


$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.031868934631348 s
  


$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.047234058380127
  
测试结果
&#20540;传递:0.031868934631348 s

引用传递: 0.047234058380127
结果引用传递性能下降:50%左右。

所以不正确使用引用,性能反而下降。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-104562-1-1.html 上篇帖子: discuz论坛程序的PHP加密函数原理 下篇帖子: PHP 二维数组根据某个字段排序
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表