概念:在php中 引用 意味着用不同的名字访问同一个变量内容,这并不像 C 的指针:例如你不能对他们做指针运算,他们并不是实际的内存地址, 定义方式:用&来定义。
一.理解引用
1.程序一
COW机制:当不使用引用时,变量采取写时复制的机制(COW),即 copy on write
memory_get_usage()函数返回分配给 PHP 的内存量字节数
range()函数 创建一个包含指定范围的元素的数组。
$a = range(0,1000);//定义一个变量a
var_dump(memory_get_usage());//打印内存使用情况
$b = $a;//定义一个变量b, 把a复制给b,这个时候内部 a与b指向同一个内容空间
var_dump(memory_get_usage());//所以这个时候内存变化不多
$a = range(0,1000);//当a或者b发生改变时候,才会重新开辟一个内存来存储变量。
var_dump(memory_get_usage());//第三次内存明显改变
/*
输出结果
int 378872
int 378960
int 515472
*/
2.程序二
当变量使用引用的时候,就不会有cow机制。
$a = range(0,1000);//定义一个变量a
var_dump(memory_get_usage());//
$b = &$a;//定义一个变量b, 把a复制给b,这个时候内部 a与b指向同一个内容空间
var_dump(memory_get_usage());//
$a = range(0,1000);//由于b对a用了引用,也就是说b和a一样都指向统一个内存地址
var_dump(memory_get_usage());//所以这个内存始终没有发现较大变化
/*
输出结果
int 378872
int 378960
int 378896
*/
3.程序三
通过zval变量容器进行验证,php的变量都在zval的容器里。
xdebug_debug_zval()查看zval结构体,需要安装xdebug插件
xdebug_debug_zval()打印 指向内存空间的变量数,和是否被引用。函数传入变量名,会打印出来两个值refcount, is_ref,refcount表示zval变量容器的个数, is_ref布尔值,真表示引用。
unset()函数 ,unset一个引用的变量,只是断开了变量名和变量内容之间的绑定。这并不意味着变量内容被销毁了。
$a = 99;
xdebug_debug_zval('a');// //(refcount=1, is_ref=0),int 99
$b = &$a;//使用 引用
xdebug_debug_zval('a');//(refcount=2, is_ref=1),int 99
$b = 99;
xdebug_debug_zval('a');//(refcount=2, is_ref=1),int 99
unset($b);//会取消引用,不销毁空间,也就是变量a还是有的,内容也不变
xdebug_debug_zval('a');//(refcount=1, is_ref=0),int 99
4.程序四
对象本身就是引用传递
class Person{ public $name='tacks';}
$p1 = new Person();
xdebug_debug_zval('p1');
$p2 = $p1;
xdebug_debug_zval('p1');
$p2->name = 'php';
xdebug_debug_zval('p1');
#上面的代码修改p2对象的name时,p1对象的name也会变化,也就是对象默认时引用传值,而不是变量的cow机制
#如果不是将$p2 = $p1;赋值改成克隆$p2 = clone $p1;,那么这两个对象就是两块内存空间了。
5.程序五
引用作为函数参数传递时,是可以被函数内部更改的
function foo(&$var){
$var++;
}
$a = 5;
foo($a);
echo $a;//6
6.程序六
如果对一个未定义的变量进行引用赋值、引用参数传递或引用返回,则会自动创建该变量。
function t(&$val){
}
$b = array();
t($b['b']);
var_dump(array_key_exists('b', $b)); // bool(true)
$c = new StdClass;
t($c->d);
var_dump(property_exists($c, 'd')); // bool(true)
二.经典例题
1.例题一
#下面的程序运行的时候,每一次循环结束后变量$data的值是?,为什么?
$data = ['a','b','c'];
foreach($data as $k=>$v){
$v = &$data[$k];
//var_dump($data);
}
步骤: 进入foreach循环 将$data的第一个元素的键赋值给$k ,值赋给$v 第一次循环:$v = &$data[$k];也就是$v = &$data[0] = a; $v与$data[0]的地址 是一样的,都指向a的内存空间 $data=['a','b','c'] 第二次循环:$k=1;$v=b; $data[0]=b;($v与$data[0]的地址 是一样的) $v=&$data[1]=b;也就是$v和$data[1]都指向b的内存空间 $data=['b','b','c'] 第三次循环:$k=2;$v=c; $data[1]=c;($v与$data[1]的地址 是一样的) $v=&$data[2]=c; 也就是$v和$data[1]都指向c的内存空间$data=['b','c','c']
2.例题二
#最后$arr的值是多少?
$arr = array('a','b','c');
foreach($arr as &$v){}
foreach($arr as $v){}
//var_dump($arr);
数组执行foreach循环时,是通过移动数组内部指针来实现的。 在第一个foreach结束的时候 $v是引用变量与$arr[2]指向同一个地址空间 所以之后$v的任何修改都会影响到$arr, 在第二个foreach的循环开始, 第一轮:$v=a 所以$arr[2]也是a arr数组的内容就是['a','b','a'] 第二轮:$v=b 所以$arr[2]也是b arr数组的内容就是['a','b','b'] 第三轮:$v=b 所以$arr[2]也是b arr数组的内容就是['a','b','b'] 打印出来的arr就是下面的结果,同时$arr[2]还是引用. array (size=3) 0 => string 'a' (length=1) 1 => string 'b' (length=1) 2 => &string 'b' (length=1)
3.例题三
#最终$arr的值?
$arr = [1,2,3];
foreach($arr as &$value){
$value *= $value;
}
foreach($arr as $value){
echo $value;
}
//var_dump($arr);
通过在 $value 之前加上 & 来修改数组的元素。此方法将以引用赋值而不是拷贝一个值。
所以第一个foreach过后$arr的值会被改变[1,4,9],同时$arr[2]是与$value引用
第二个foreach中$arr[2]是与$value引用
第一次循环 $value=1 $arr[2]=1 所以$arr=[1,4,1];
第二次循环 $value=4 $arr[2]=4 所以$arr=[1,4,4];
第三次循环 $value=4 $arr[2]=4 所以$arr=[1,4,4];
三.小结
基本的引用变量的题目,只要理解&的意思,按照步骤一步一步来,基本上都可以知道程序的答案