一、起因
<?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前面不要加 & 符号,但是在函数“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 a。
<?php
$a = 1;
$b = & $a;
print($a); // 1
print($b); // 1
unset($a);
print($a); // Undefined variable: a
print($b); // 1
使用unset(a=null的结果是不一样的。如果该块内存只有a)与a和a)将导致b不变的情况,而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是相同的,但是并没有像C那样,b占用不同的内存空间,而是指向了同一块内存,这就是php和c的差别,并不需要写成a才表示a的内存,zend就已经帮你实现了引用,并且zend会非常智能的帮你去判断什么时候该这样处理,什么时候不该这样处理。
如果在后面继续写如下代码,增加一个函数,通过引用的方式传递参数,并打印输出数组大小。
function printArray(&$arr) //引用传递
{
print(count($arr));
}
printArray($a);
上面的代码中,我们通过引用把a的改变,此时就会自动为a的数据拷贝,重新申请一块内存进行存储。这就是前面提到的“引用计数、写时拷贝”概念。
直观的理解:b,则会使用新开辟的内存空间,而这个空间将使用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%左右。
所以不正确使用引用,性能反而下降。
网友评论