文档章节

php变量和对象

小疯子的逆时光
 小疯子的逆时光
发布于 2017/03/16 16:05
字数 3884
阅读 4
收藏 0

一,php变量

php变量分变量名符号和变量内容,变量符号存在符号表中,而变量内容存储在zval变量容器,具体介绍:

1,zval结构

typedef union _zvalue_value {
    long lval;                  /* long value */
    double dval;                /* double value */
    struct {
        char *val;
        int len;
    } str;
    HashTable *ht;              /* hash table value */
    zend_object_value obj;
    zend_ast *ast;
} zvalue_value;

struct _zval_struct {
    zvalue_value value;     /*描述变量的值*/
    zend_uint refcount__gc;
    zend_uchar type;        /*描述变量的类型*/
    zend_uchar is_ref__gc;
};

_zval_strust包含typevalue,另外包含两个字节的额外信息,is_ref:bool值,标示这个变量是否是属于引用集合(reference set),php引擎通过is_ref才能把普通变量和引用变量区分开来。refcount:标示指向这个zval变量容器的变量(也称符号即symbol)个数,用来优化内存使用。

符号表:

所有的变量符号存在一个符号表中,符号有自己的作用域,比如每个函数或方法域,主脚本域。

普通变量类型:

当一个变量被赋常量值时,就会生成一个zval变量容器,Example #1

<?php

$a = "new string";             // 生成一个新的zval容器

xdebug_debug_zval('a'); //结果:a: (refcount=1, is_ref=0),string 'new string' (length=11)

$c = $b = $a;                  // 把一个变量赋值给另一变量,增加一个引用计数

xdebug_debug_zval('a'); //结果:a: (refcount=3, is_ref=0),string 'new string' (length=11)

$b = '123';

xdebug_debug_zval('a'); //结果:a: (refcount=2, is_ref=0),string 'new string' (length=11)

unset($c);                   //变量调用了函数 unset()时,”refcount“就会减1

xdebug_debug_zval('a'); //结果:a: (refcount=1, is_ref=0),string 'new string' (length=11)

$d = &$a;

xdebug_debug_zval('a'); //结果:a: (refcount=2, is_ref=1),string 'new string' (length=11)

unset($a);                    //包含类型和值的这个变量容器就会从内存中删除。

xdebug_debug_zval('a'); //结果:a: no such symbol

?>

定义了变量a,在当前域中生成类型为string,值位new string的变量容器,此时没有任何自定义引用,is_ref默认为0,只有一个变量在使用,refcount默认是1,安装Xdebug,调用函数 xdebug_debug_zval()显示"refcount"和"is_ref"的值。

b变量=a,同一个变量容器被变量a和变量b关联,php不会复制已生成的变量容器,refcount变成2,unset变量时,refcount减1、当任何关联到某个变量容器的变量离开它的作用域(比如:函数执行结束)也会减1,当refcount变成0时就被销毁。

复合类型(Compound Types)

array和object符合类型,与普通类型不同,符合类型把成员和属性存在自己的符号表,会有多个zval变量容器

Example #2 

<?php

$a = array( 'meaning' => 'life', 'number' => 42 );    //Creating a array zval

xdebug_debug_zval( 'a' );

/* 结果:
a:(refcount=1, is_ref=0),
array (size=2)
  'meaning' => (refcount=1, is_ref=0),string 'life' (length=4)
  'number' => (refcount=1, is_ref=0),int 42
*/

?>

这三个zval变量容器是: a,meaning和 number。增加和减少”refcount”的规则和上面提到的一样

Example #3 添加一个已经存在的元素到数组中

<?php

$a = array( 'meaning' => 'life', 'number' => 42 );

$a['life'] = $a['meaning'];   

xdebug_debug_zval( 'a' );

/*结果:
a:
(refcount=1, is_ref=0),
array (size=3)
  'meaning' => (refcount=2, is_ref=0),string 'life' (length=4)
  'number' => (refcount=1, is_ref=0),int 42
  'life' => (refcount=2, is_ref=0),string 'life' (length=4)
*/

?>

原有的数组元素和新添加的数组元素关联到同一个zval变量容器,输出显示两个值为'life'的 zval 变量容器,其实是同一个。 

Example #4 从数组中删除一个元素

<?php

$a = array( 'meaning' => 'life', 'number' => 42 );

$a['life'] = $a['meaning'];

unset( $a['meaning'], $a['number'] );

xdebug_debug_zval( 'a' );

/*结果:
a:
(refcount=1, is_ref=0),
array (size=1)
  'life' => (refcount=1, is_ref=0),string 'life' (length=4)
*/

?>

删除数组中的一个元素,就是类似于从作用域中删除一个变量。删除后,数组中的这个元素所在的容器的“refcount”值减少,同样,当“refcount”为0时,这个变量容器就从内存中被删除

现在,当我们添加一个数组本身作为这个数组的元素时,事情就变得有趣,下个例子将说明这个。例中我们加入了引用操作符,否则php将生成一个复制。

Example #5 把数组作为一个元素添加到自己

<?php

$a = array( 'one' );

$a[] =& $a;

xdebug_debug_zval( 'a' );

/*结果:
a:
(refcount=2, is_ref=1),
array (size=2)
  0 => (refcount=1, is_ref=0),string 'one' (length=3)
  '' => (refcount=2, is_ref=1),
    &array<
*/

?>

如果此时unset($a),将删除这个符号,它指向的变量容器引用计数减1,尽管不再有某个作用域中的任何符号指向这个结构(就是变量容器),由于数组元素“1”仍然指向数组本身,所以这个容器不能被清除,php将在脚本执行结束时清除这个数据结构,但是在php清除之前,将耗费不少内存。

如果你要实现分析算法,或者要做其他像一个子元素指向它的父元素这样的事情,这种情况就会经常发生。当然,同样的情况也会发生在对象上,实际上对象更有可能出现这种情况,因为对象总是隐式的被引用。

如果上面的情况发生仅仅一两次倒没什么,但是如果出现几千次,甚至几十万次的内存泄漏,这显然是个大问题。

二,引用计数

引用意味着用不同的名字访问同一个变量内容,是变量的另外一个别名或者映射。引用可以被看作是 Unix 文件系统中的紧密连接或者wins的快捷方式。

变量的引用

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

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

这意味着 $a 和 $b 指向了同一个变量。

注: $a 和$b 在这里是完全相同的,这并不是$a 指向了$b 或者相反,而是$a 和$b 指向了同一个地方。

对象的引用(对象的赋值传递)

在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 
 */  

其实等效于$b=new fooclass(); $c=&$b;
PHP5中默认就是通过引用来调用对象, 但有时你可能想建立一个对象的副本,并希望原来的对象的改变不影响到副本 . 为了这样的目的,PHP5定义了一个特殊的方法,称为__clone。

自 PHP 5 起,new 自动返回引用,因此在此使用 =& 已经过时了并且会产生 E_STRICT 级别的消息。

在php4中,对象的赋值是个拷贝过程,

$a = new foo fooclass(), 实际上$a和new fooclass()的映射到不同对象实例,其中new fooclass产生的是一个匿名的fooclass对象实例 所以需要显式地使用$a = & new fooclass()来进行引用赋值传递。

所以在php4中,为了节省内存空间,$b=new fooclass()一般会改成引用的模式,即 $b=& new fooclass()。

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

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

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

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

<?php
function  test( & $b ){  
     $b ++ ;  
}  
$c = 0 ;  
call_user_func_array ( 'test' , array ( & $c ));  
echo   $c ;  

函数的引用返回

<? 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();得到的其实不是函数的引用返回,这跟普通的函数调用没有区别,只是将函数的值赋给$a而已, 而$a做任何改变 都不会影响到函数中的$b。
而通过$a=&test()方式调用函数呢, 他的作用是 将return $b中的 $b变量的内存地址与$a变量的内存地址 指向了同一个地方
即产生了相当于这样的效果($a=&$b;) 所以改变$a的值 也同时改变了$b的值 所以在执行了

$a=&test();
$a=5;

以后,$b的值变为了5。

注意:

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

& test()指出返回的是一个引用,而不是通常的一个拷贝。
$a =& test()同样也指出 $a 是作为引用的绑定,而不是通常的赋值。

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

 

[php] view plain copy print?

  1. <?php  
  2. /** 
  3.  * This is the way how we  use  pointer to access variable inside the  class . 
  4.  */  
  5. class  talker{  
  6.      private   $data   =   ' Hi ' ;  
  7.      public   function   &  get(){  
  8.          return   $this -> data;  
  9.     }  
  10.     public   function  out(){  
  11.          echo   $this -> data;  
  12.     }    
  13. }  
  14. $aa   =   new  talker();  
  15. $d   =   & $aa -> get();  
  16. $aa -> out();  
  17. $d   =   ' How ' ;  
  18. $aa -> out();  
  19. $d   =   ' Are ' ;  
  20. $aa -> out();  
  21. $d   =   ' You ' ;  
  22. $aa -> out();  
  23. //the output is  " Hi How Are You "  

6 global 引用

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

[php] view plain copy print?

  1. <?php  
  2.      $var   =&   $GLOBALS [ "var" ];  
  3. ?>    

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

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

[php] view plain copy print?

  1. <?php  
  2. global $var;  
  3. $var = "Example variable";  
  4. $var_test =  &  $GLOBALS [ 'var' ];  
  5. echo '$var_test is set to ',$var_test,'<br>'; //  
  6. $var_test = 11;  
  7. unset($var_test);  
  8. echo '$GLOBALS[var] is set to ',$GLOBALS ['var'],'<br>';  
  9. /** 
  10.  * 输出结果: 
  11. $var_test is set to Example variable 
  12. $GLOBALS[var] is set to 11 
  13.  */  

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

[php] view plain copy print?

  1. $var1 = "Example variable";  
  2. $var2 = "";  
  3. function global_references($use_globals) {  
  4.     global $var1, $var2;  
  5.     if (!$use_globals) {  
  6.         $var2 =& $var1; // visible only inside the function  
  7.     } else {  
  8.         $GLOBALS["var2"] =& $var1; // visible also in global context  
  9.     }  
  10. }  
  11. global_references(false);  
  12. echo "var2 is set to '$var2'", '<br>'; // var2 is set to ''  
  13. global_references(true);  
  14. echo "var2 is set to '$var2'"; // var2 is set to 'Example variable'  

[php] view plain copy print?

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

那么实际上就是改变了$var2的映射关系,之前与$_GLOBAL['var2']的映射就断开了,再也无法修改函数外部$var2的内容。此特性也同样作用在用引用传递函数参数的例子中。
另外,需要注意在函数中unset($var2)不会真正释放变量内容,可以看做只是断开了函数内部$var2的映射而已。


7 . 取消引用 unset与=null

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

[php] view plain copy print?

  1. <?php  
  2.   
  3.      $a   =   1 ;  
  4.      $b   =&   $a ;  
  5.      unset  ( $a );  
  6.      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的情况。
原因:某变量赋值为null,将导致该变量对应的内存块的引用计数直接置为0,被自动回收。

8 . $this

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

9. PHP引用使用注意事项

很多人误解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会非常智能的帮你去判断什么时候该这样处理,什么时候不该这样处理。

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

[php] view plain copy print?

  1. function printArray(&$arr) //引用传递  
  2.  {  
  3.     print(count($arr));  
  4. }  
  5.  printArray($a);  


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

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

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

[php] view plain copy print?

  1. function printArray($arr)  //值传递  
  2.  {  
  3.      print(count($arr));  
  4.  }  
  5.  printArray($a);  

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

测试:

[php] view plain copy print?

  1. $a = array(1,2,3);  
  2. $b =$a;  
  3. function printArray($arr)  //值传递  
  4. {  
  5.     print(count($arr));  
  6. }  
  7. $s = microtime(true);   
  8. for($i=1;$i<10000;$i++){  
  9.     printArray($a);  
  10.  }  
  11. $e = microtime(true);   
  12. echo '=----------------------------';  
  13. echo $e-$s;//0.031868934631348 s  

 

 

[php] view plain copy print?

  1. $a = array(1,2,3);  
  2. $b = $a;  
  3. function printArray(&$arr)  //引用传递  
  4. {  
  5.     print(count($arr));  
  6. }  
  7. $s = microtime(true);   
  8. for($i=1;$i<10000;$i++){  
  9.     printArray($a);  
  10.  }  
  11. $e = microtime(true);   
  12. echo '=----------------------------';  
  13. echo $e-$s;//0.047234058380127  

 

测试结果

值传递:0.031868934631348 s
引用传递: 0.047234058380127

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

举例:

如果在函数里面:php的变量是有生命期间的, 一个函数里面申请的变量的生命只限于这个函数内部,当这个函数执行完毕,它分配的内存都会被销毁。

对象new后是直接传递引用的,$obj=new stdclass;obj是对象标识符的拷贝,都指向同一块内存,函数执行完毕后对象标识符消除,但是全局依然存在;$obj=&new stdclass;obj是对象的引用别名,函数执行完后对象消除,obj也就跟着消除了。

 

© 著作权归作者所有

小疯子的逆时光
粉丝 0
博文 31
码字总数 30892
作品 0
程序员
私信 提问
php的引用(就是在变量或者函数、对象等前面加上&符号)

php的引用(就是在变量或者函数、对象等前面加上&符号) 在PHP 中引用的意思是:不同的名字访问同一个变量内容. 与C语言中的指针是有差别的.C语言中的指针里面存储的是变量的内容在内存中...

晨曦之光
2012/03/09
1K
0
php中引用&的真正理解

原文地址:http://www.cnblogs.com/thinksasa/p/3334492.html php的引用(就是在变量或者函数、对象等前面加上&符号) //最重要就是 删除引用的变量 ,只是引用的变量访问不了,但是内容并没...

chenzy526
2017/05/26
0
0
PHP超全局变量、魔术变量和魔术方法

PHP在设计的时候已经预定义了9个超全局变量、8个魔术变量和13魔术方法,这些变量和函数可以在脚本的任何地方不用声明就可以使 用。 在PHP开发会频繁的使用这些变量和函数,这些变量和函数可以...

Weltch
2016/10/15
545
0
PHP的static与Java的static的对比

在Java中类的静态变量/静态方法又称之为类变量 /类方法,它们存在于内存栈中,只有一份,可以通过类和对象直接访问 public class MyStatic { public static String className = "MyStatic";...

big_cat
2015/09/30
303
2
PHP超级全局变量、魔术变量和魔术函数

PHP在设计的时候已经预定义了9个超级全局变量、8个魔术变量和13魔术函数,这些变量和函数可以在脚本的任何地方不用声明就可以使用。 在PHP开发会频繁的使用这些变量和函数,这些变量和函数可...

a928154159
2018/02/22
0
0

没有更多内容

加载失败,请刷新页面

加载更多

PostgreSQL 11.3 locking

rudi
今天
5
0
Mybatis Plus sql注入器

一、继承AbstractMethod /** * @author beth * @data 2019-10-23 20:39 */public class DeleteAllMethod extends AbstractMethod { @Override public MappedStatement injectMap......

一个yuanbeth
今天
11
1
一次写shell脚本的经历记录——特殊字符惹的祸

本文首发于微信公众号“我的小碗汤”,扫码文末二维码即可关注,欢迎一起交流! redis在容器化的过程中,涉及到纵向扩pod实例cpu、内存以及redis实例的maxmemory值,statefulset管理的pod需要...

码农实战
今天
4
0
为什么阿里巴巴Java开发手册中不建议在循环体中使用+进行字符串拼接?

之前在阅读《阿里巴巴Java开发手册》时,发现有一条是关于循环体中字符串拼接的建议,具体内容如下: 那么我们首先来用例子来看看在循环体中用 + 或者用 StringBuilder 进行字符串拼接的效率...

武培轩
今天
9
0
队列-链式(c/c++实现)

队列是在线性表功能稍作修改形成的,在生活中排队是不能插队的吧,先排队先得到对待,慢来得排在最后面,这样来就形成了”先进先出“的队列。作用就是通过伟大的程序员来实现算法解决现实生活...

白客C
今天
81
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部