PHP 引用变量总结

变量引用对比赋值操作

赋值操作:
// 定义一个变量
$a = range(0, 1000);  // 在内存中申请空间来存储变量a对应的值
var_dump(memory_get_usage());

// 定义变量b,将a变量的值赋值给b
$b = $a;
// 由于PHP的COW,(Copy On Write,只有在变量发生“写”操作之后,系统才会申请新的内存空间)所以,此时,变量a与变量b会指向同一个内存地址。
var_dump(memory_get_usage());

// 对a进行修改
$a = range(0, 1000);
// 变量a发生了“写”操作,系统将在内存中申请新的空间,保存新的值,同时变量a指向原内存地址的指针将会移除,并指向新的内存地址。
var_dump(memory_get_usage());
引用变量

PHP的引用变量定义符为 &
该操作将会将原有变量的指向的内存地址传递给新的变量。

如:

$a = range(0, 1000);   //在内存中开辟空间用于保存 $a 对应的值
$b = &$a;              //将 $a 对应的值在内存中的地址空间传递给 $b , 此时 $a 与 $b 共用同一块内存空间。此时,如果对 $a 或者 $b 进行修改,则 $a 与 $b 的值将同时修改。

两者区别

引用变量传递了内存地址,如果有一个变量的值发生修改,那么两个变量的值都会发生变化;
变量赋值变量修改只会修改发生变化的那个变量的值;在未发生修改之前,与引用变量一样,两者都会指向同一个内存地址

zval变量容器变化

赋值操作
// zval变量容器
$a = range(0, 3);
xdebug_debug_zval('a');
/***
  * 输出结果
  * a: (refcount=1, is_ref=0)=array(4) {   // refcount=1:引用计数为1,只有变量a;is_ref=0:是否引用变量:false
  *     [0] =>(refcount=0, is_ref=0)=int(0)
  *     [1] =>(refcount=0, is_ref=0)=int(1)
  *     [2] =>(refcount=0, is_ref=0)=int(2)
  *     [3] =>(refcount=0, is_ref=0)=int(3)
  * }
  ***/
// 定义变量b,把a的值赋值给b
$b = $a;
xdebug_debug_zval('a');
/***
  * 输出结果
  * a: (refcount=2, is_ref=0)=array(4) {   // refcount=2:引用次数为2,变量a与变量b同时指向a对应的内存空间;is_ref=0:是否引用变量:false
  *     [0] =>(refcount=0, is_ref=0)=int(0)
  *     [1] =>(refcount=0, is_ref=0)=int(1)
  *     [2] =>(refcount=0, is_ref=0)=int(2)
  *     [3] =>(refcount=0, is_ref=0)=int(3)
  * }
  ***/
// 修改a
$a = range(0, 3);
xdebug_debug_zval('a');
/***
  * 输出结果
  * a: (refcount=1, is_ref=0)=array(4) {   // refcount=1:引用计数为1,由于COW原则,变量a指向了新的内存地址,所以只有变量a;is_ref=0:是否引用变量:false
  *     [0] =>(refcount=0, is_ref=0)=int(0)
  *     [1] =>(refcount=0, is_ref=0)=int(1)
  *     [2] =>(refcount=0, is_ref=0)=int(2)
  *     [3] =>(refcount=0, is_ref=0)=int(3)
  * }
  ***/

引用变量

$a = range(0, 3);
xdebug_debug_zval('a');
/***
  * 输出结果
  * a: (refcount=1, is_ref=0)=array(4) {   // refcount=1:引用计数为1,只有变量a;is_ref=0:是否引用变量:false
  *     [0] =>(refcount=0, is_ref=0)=int(0)
  *     [1] =>(refcount=0, is_ref=0)=int(1)
  *     [2] =>(refcount=0, is_ref=0)=int(2)
  *     [3] =>(refcount=0, is_ref=0)=int(3)
  * }
  ***/
// 引用变量
$b = &$a;
xdebug_debug_zval('a');
/***
  * 输出结果
  * a: (refcount=2, is_ref=1)=array(4) {   // refcount=2:引用计数为2,变量a与变量b同时指向a对应的内存空间;is_ref=1:是否引用变量:true
  *     [0] =>(refcount=0, is_ref=0)=int(0)
  *     [1] =>(refcount=0, is_ref=0)=int(1)
  *     [2] =>(refcount=0, is_ref=0)=int(2)
  *     [3] =>(refcount=0, is_ref=0)=int(3)
  * }
  ***/
// 修改变量a
$a = range(0, 3);
xdebug_debug_zval('a');
/***
  * 输出结果
  * a: (refcount=2, is_ref=1)=array(4) {   // refcount=2:引用计数为2,变量a与变量b同时指向a对应的内存空间;is_ref=1:是否引用变量:true;并未受到修改操作的影响
  *     [0] =>(refcount=0, is_ref=0)=int(0)
  *     [1] =>(refcount=0, is_ref=0)=int(1)
  *     [2] =>(refcount=0, is_ref=0)=int(2)
  *     [3] =>(refcount=0, is_ref=0)=int(3)
  * }
  ***/

unset操作

unset操作仅会取消引用,而不会销毁内存空间.

$a = 1;
$b = &$a;
unset($b);  // 此时只取消了变量b的引用,而不会销毁内存空间,变量a不受影响
echo $a;

对象操作

对象本身就是引用传递!!!!如果需要复制,参考 clone

class Person
{
    public $name = "zhangsan";
}

$p1 = new Person;
xdebug_debug_zval('p1');
/***
  * p1: (refcount=1, is_ref=0)=class Person#1 (1) {
  *     public $name =>(refcount=2, is_ref=0)=string(8) "zhangsan"
  * }
 ***/
$p2 = $p1;
xdebug_debug_zval('p1');
/***
  * p1: (refcount=2, is_ref=0)=class Person#1 (1) {
  *     public $name =>(refcount=2, is_ref=0)=string(8) "zhangsan"
  * }
 ***/
$p2->name = "lisi";
xdebug_debug_zval('p1');
/***
  * p1: (refcount=2, is_ref=0)=class Person#1 (1) {
  *     public $name =>(refcount=2, is_ref=0)=string(8) "lisi"
  * }
 ***/

Example

$data = ['a', 'b', 'c'];

foreach ($data as $key=>$val)
{
    $val = &$data[$key];
}

var_dump($data);  // 输出['b', 'c', 'c']
/***
  * 分析:
  * foreach循环中,
  * 第一次循环:
  *     $data as $key => $val: $val = $data[0] (即'a'), $key = 0, 进入循环体;
  *     由于是引用变量操作,所以 $val 与 $data[0] 均会指向 ‘a’,第一次循环结束;
  * 第二次循环:
  *     $data as $key => $val: $val = $data[1] (即'b'),由于上一步循环中,$val 与 $data[0]是指向的同一个内存地址,且为引用变量,所以,此时$data[0]值则变为了‘b’,进入循环体
  *     由于是引用变量操作,所以 $val 与 $data[1] 均会指向 ‘b’,第二次循环结束;
  * 第三次循环:
  *     $data as $key => $val: $val = $data[2] (即'c'),由于上一步循环中,$val 与 $data[1]是指向的同一个内存地址,且为引用变量,所以,此时$data[1]值则变为了‘c’,进入循环体
  *     由于是引用变量操作,所以 $val 与 $data[1] 均会指向 ‘c’,第三次循环结束;
  * 循环结束后,$data[0] = 'b',$data[1] = 'C',$data[2] = 'c',
  * 所以最后输出结果为['b', 'c', 'c']
 ***/