在 PHP 中,当变量前面出现 “&” 符号时,它表示引用,意味着不同的名字可以访问同一个变量内容。这与 C 语言的指针不同,“&” 在 PHP 中是符号表别名。
例如,有以下代码:
$a = "ABC";
$b = &$a;
echo $a; //这里输出:ABC
echo $b; //这里输出:ABC
$b = "EFG";
echo $a; //这里$a 的值变为 EFG 所以输出 EFG
echo $b; //这里输出 EFG
从这个例子可以看出,一旦变量被引用,对引用变量的操作会直接影响到被引用的变量。
在 PHP 中,变量名和变量内容是不一样的,因此同样的内容可以有不同的名字。最接近的比喻是 Unix 的文件名和文件本身,变量名是目录条目,而变量内容则是文件本身。引用可以被看作是 Unix 文件系统中的紧密连接。
与 C 语言的指针相比,PHP 的引用不是指针。比如在 C 语言中,释放一个指向变量的指针可能会导致变量不可访问或出现错误,但在 PHP 中,即使取消引用,只是断开了变量名和变量内容之间的绑定,并不意味着变量内容被销毁。例如:
$a = 1;
$b = &$a;
unset($a);
echo $b; //不会 unset $b,只是 $a,这里仍输出 1。
二、变量引用
(一)变量引用的示例
以下是一个更复杂的变量引用示例:
$data = [ 'a', 'b', 'c' ];
foreach ( $data as $key => $val ){
$val = & $data [ $key ];
}
var_dump( $data );
在这个例子中,通过循环将数组中的每个元素进行引用赋值。这样,当对其中一个元素进行修改时,其他引用该元素的变量也会相应地发生变化。比如,如果在循环后执行$data[0] = 'd';,那么所有引用这个位置的变量都会显示新的值'd'。
再看一个例子:
$arr = array('name'=>'baixiaoshi','age'=>23);
echo'<pre>';
print_r($arr);
echo'</pre>';
$arr2 = $arr;
$arr2['hobby'] = 'run';
echo'<pre>';
print_r($arr2);
echo'</pre>';
这里虽然一开始$arr2是对$arr的复制,但当修改$arr2时,实际上也影响了原始数组$arr的内容,这在一定程度上展示了变量引用的潜在影响,虽然这里不是直接使用 “&” 进行引用,但说明了变量之间的关联可能会导致意外的结果。
(二)取消引用的影响
当取消引用时,只是断开了变量名和变量内容之间的绑定,这意味着变量内容不会被销毁。例如:
$a = 1;
$b = &$a;
unset($b);
echo $a; //这里仍输出 1。
在这个例子中,取消引用$b后,$a的值并没有受到影响,只是$b不再与$a有任何关联。
再如:
$a = range(0,1000);
var_dump(memory_get_usage());
$b = & $a ;
var_dump(memory_get_usage());
unset($b);
var_dump(memory_get_usage());
在这个例子中,使用memory_get_usage()函数可以观察到内存的变化。当进行引用赋值后,内存的占用情况会有所变化。而当取消引用$b后,内存并没有立即释放,这进一步说明了取消引用只是断开了变量名和变量内容之间的绑定,而不是销毁变量内容。
三、函数引用
(一)函数的传址调用
在 PHP 中,函数可以通过引用传递变量。这意味着函数内对参数的修改会直接影响到外部的变量。例如:
function test(&$a){
$a=$a+100;
}
$b=1;
echo $b ;//输出1
test($b);
echo "<br>";
echo $b;//输出 101
在这个例子中,函数test接受一个引用参数$a,在函数内部对$a进行操作,将其值增加 100。由于参数是通过引用传递的,所以外部变量$b的值也被修改了。
需要注意的是,PHP 规定传递的引用不能为常量。如果尝试test(1);,会出现错误。
(二)函数的引用返回
在 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=test()这种方式调用函数,只是将函数的值赋给$a而已,而$a做任何改变,都不会影响到函数中的$b。而通过$a=&test()方式调用函数呢,它的作用是将return $b中的$b变量的内存地址与$a变量的内存地址指向了同一个地方。即产生了相当于这样的效果($a=&$b;),所以改变$a的值也同时改变了$b的值。所以在执行了$a=&test();$a=5;以后,$b的值变为了 5。
引用返回用在当想用函数找到引用应该被绑定在哪一个变量上面时。当返回引用时,使用此语法:function &find_var ($param){/*...code... */return $found_var;}。这里必须在两个地方都用&符号,指出返回的是一个引用,也指出变量是作为引用的绑定。如果试图这样从函数返回引用:return ($this->value);,这将不会起作用,因为在试图返回一个表达式的结果而不是一个引用的变量。只能从函数返回引用变量。
四、对象引用
(一)对象引用的示例
在 PHP5 中,对象的复制是通过引用来实现的。例如:
class TvControl {}
class Tv {
private $color;
private $tvControl;
function __construct() {
$this->color = "black";
$this->tvControl = new TvControl();
}
function setColor($color) {
$this->color = $color;
}
function getColor() {
return $this->color;
}
function getTvControl() {
return $this->tvControl;
}
}
$tv1 = new Tv();
$tvControl1 = $tv1->getTvControl();
$tv2 = $tv1;
echo "引用类:";
var_dump($tv2);
在这个例子中, tv1 的引用,当修改 tv2 的对应属性也会发生变化。例如,如果执行 $tv1->setColor("white");,那么 $tv2->getColor() 也会返回 "white"。
再看另一个例子:
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
这里同样展示了对象引用的效果, c 指向同一个对象,当 c 的对应属性也随之改变。
(二)特殊方法__clone
PHP 为了实现建立一个对象的副本,且希望原来的对象的改变不影响到副本,定义了一个特殊的方法称为 __clone。
例如:
class Cat {
public $name;
function __construct($name) {
echo 'Cat类启动';
$this->name = $name;
}
function __destruct() {
echo 'Cat类结束';
}
}
$a = new Cat("默默");
$b = clone $a;
echo "改变之前:<br>";
echo "a->name:". $a->name. "<br>";
echo "b->name:". $b->name. "<br>";
$a->name = "琳琳";
echo "改变之后:<br>";
echo "a->name:". $a->name. "<br>";
echo "b->name:". $b->name. "<br>";
在这个例子中,使用 clone 关键字创建了对象的副本 a 的属性时,副本 $b 的属性不会发生变化。
当使用 clone 关键字时,如果被 “克隆” 的类属性中的引用,则该引用被保留了,也就是说,副本中的引用与原类中的引用都指向同样的内存。为了解决这个问题,可以在类中定义 __clone 方法来调整对象的克隆行为。例如:
class Corporate_Drone {
// 属性和方法定义
function __clone() {
// 在这里可以对克隆的对象进行特定的处理
}
}
$original = new Corporate_Drone();
$cloned = clone $original;
通过在 __clone 方法中添加特定的逻辑,可以实现更灵活的对象克隆。例如,可以在克隆时对某些属性进行初始化或者修改,以满足特定的需求。
五、引用的作用及其他情况
(一)引用在程序中的作用
在程序较大的情况下,如果引用同一个对象的变量比较多,使用引用可以更方便地管理和操作这些变量。因为通过引用,可以直接对同一个对象进行操作,而不需要通过复制对象来传递数据,这样可以节省内存空间和提高程序的执行效率。例如,在处理大型数据结构时,如大数组,如果使用引用传递,可以避免不必要的内存复制,从而提高程序的性能。建议在这种情况下使用 “&” 方式进行引用,并且在使用完对象后,可以通过将引用变量设置为null的方式来手工清除对象,以释放内存资源。
(二)global 引用
在 PHP 中,当用global $var声明一个变量时,实际上建立了一个到全局变量的引用。这意味着它等价于$var = &$GLOBALS["var"];。例如:
$var1 = 1;
$var2 = 2;
function test_global_ref() {
global $var1,$var2;
$var1 = &$var2;
$var1 = 7;
}
test_global_ref();
echo $var1;
echo $var2;
在这个例子中,函数内对全局变量的引用操作导致了变量值的变化。使用global声明变量建立的引用,使得在函数内部可以直接操作全局变量,而不是创建一个局部副本。但是需要注意的是,使用global可能会导致代码的可读性和可维护性降低,因为全局变量的作用域较大,容易引起命名冲突和意外的变量修改。
(三)“this” 引用
在 PHP 的对象方法中,“this” 永远是调用它的对象的引用。例如:
class Person {
public $name;
public function setName($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
$person = new Person();
$person->setName('John');
echo $person->getName(); //输出:John
在这个例子中,在setName方法中,使用$this->name来访问当前对象的$name属性,并将传递给该方法的参数分配给该属性。在getName方法中,同样使用$this->name来访问$name属性,并返回该属性的值。“this” 关键字允许在对象的方法中访问当前对象的属性和方法,是 PHP 面向对象编程中非常重要的一部分。
(四)“写时拷贝” 原理
PHP 引用采用的是 “写时拷贝” 的原理,就是除非发生写操作,指向同一个地址的变量或者对象是不会被拷贝的。例如:
$a = "ABC";
$b = $a;
//此时$a与$b都是指向同一内存地址而并不是$a与$b占用不同的内存
$a = "EFG";
//由于$a与$b所指向的内存的数据要重新写一次了,此时 Zend 核心会自动判断自动为$b生产一个$a的数据拷贝,重新申请一块内存进行存储。
这种机制可以在一定程度上节省内存,只有在真正需要修改数据时才进行拷贝操作,提高了程序的性能。同时,也需要注意这种机制可能会导致一些意外的结果,特别是在处理复杂的数据结构和变量引用时,需要仔细考虑变量的修改对其他引用变量的影响。