最近在写Go语言,Go没有引用。然后给我了一些启发,就把17年的引用基础的笔记翻出来,和函数的值传递和引用传递合在一起,又水一帖。
基础知识
在PHP中引用
意味着用不同名字访问同一个变量的内容
引用的本质是别名而不是指针
引用变量
<?php
$a = range(0,100);
$b = $a;
此时b 指向的是同一块内存空间吗?是的。
PHP有一个机制叫做COW机制,也就是Copy On write 。只有b 发生写操作时,才会开辟一块新的空间,b 才指向不同的内存块。
证明:
$a = range(0,100);
var_dump(memory_get_usage());
$b = $a;
var_dump(memory_get_usage());
$a = range(0,100);
var_dump(memory_get_usage());
使用引用的话,就不会触发写时复制机制
<?php
$a = range(0,100);
var_dump(memory_get_usage());
$b = &$a;
var_dump(memory_get_usage());
$a = range(0,100);
var_dump(memory_get_usage());
unset()与引用
unset 只会取消引用不会销毁内存空间
<?php
$a = 1;
$b = &$a;
unset($b);
echo $a;
zval 结构体
PHP的变量都是基于zval结构体的,也就是说zval是PHP变量的容器
<?php
$a = range(0,100);
xdebug_debug_zval('a');
$b = $a;
xdebug_debug_zval('a');
$a = range(0,100);
xdebug_debug_zval('a');
对象与引用
在PHP中,对象默认的传值方式是引用而不是赋值。所以,不用加&。
<?php
class Person
{
public $name;
}
$p1 = new Person();
xdebug_debug_zval('p1');
$p2 = p1;
xdebug_debug_zval('p1');
$p2->name = 'fangle';
xdebug_debug_zval('p1');
foreach 与引用
<?php
$data = ['a','b','c'];
foreach($data as $key => $val){
$val = &$data[$key];
}
//每一次循环结束后$data的值是什么?
解答
$key = 0
$val = a
$val = &$data[0] = a
$key = 1
$val = b => $data[0]=b
$val = &$data[1] = b
$key = 2
$val = c => $data[1] = c
$val = &$data[2] = c
所以
$data = ['b','c','c'];
谨慎foreach中使用引用带来的影响
$test = ['a','b','c'];
foreach($test as &$value){
echo $value;
}
$num = [1,2,3]
foreach($num as $value){
echo $value;
}
一个良好的习惯是,如果你在foreach 中使用了 &$val, 应该在foreach结束后,写上一句unset($val), 防止后面同名变量误操作导致污染。记住PHP并没有行作用域变量
函数传参是传值还是传引用?
<?php
function foo(&$arr){
foreach($arr as $key=> $val){
$arr[$key] = 'hello '.$val;
}
}
$arr = ['guangdong','china','world'];
foo($arr);
var_dump($arr);
这是一个常用的函数传引用的例子。
传引用通常应用于大数组的情况。为了省内存。但是,就我个人的观点而言,我觉得这是不必要的。主要的观点如下
传引用不意味着省内存
函数传参分为以下四种情况:
- 传值,不对参数进行写操作
- 传值,对参数进行写操作
- 传引用,不对参数进行写操作
- 传引用,对参数进行写操作
于是就有下面这两段示例代码:
function foo($arr){
foreach($arr as $key=> $val){
// $arr[$key] = 'hello '.$val;
}
var_dump(memory_get_usage());
}
$arr = array_fill(0,500000,rand(1,10));
var_dump(memory_get_usage());
foo($arr);
function foo(&$arr){
foreach($arr as $key=> $val){
// $arr[$key] = 'hello '.$val;
}
var_dump(memory_get_usage());
}
$arr = array_fill(0,500000,rand(1,10));
var_dump(memory_get_usage());
foo($arr);
测试可得 :
在未对传入参数做写操作时,传值比传引用更省内存(cow机制)
当然若对传入参数做写操作时,传引用和传值带来的内存消耗区别确实很大,但是这其实也是可以避免的。
任何一个传引用,对参数进行处理的函数,其实都可以用流式操作来修改,例如上面的代码可以改为
function foo($val){
var_dump(memory_get_usage());
return "hello".$val;
}
$arr = array_fill(0,500000,rand(1,10));
foreach($arr as &$val){
$val = foo($val);
}
unset($val)
var_dump(memory_get_usage());
so 其实很多时候,我觉得值传递就可以解决问题了
传引用带来不确定性
一个不好的例子:
function getOddNum(&$arr){
$num = 0;
foreach($arr as &$val){
if($num%2 == 0){
unset($val)
}else{
$num ++;
}
}
return $num
}
$arr = array_fill(0,500000,rand(1,10));
$intOddNum = getOddNum($arr)
是的,这是一种比较极端的例子。但是这种代码,也不见得少见。而且大多数都是改来改去就改成了这个样子。
函数传引用的一个优点是说对比于指针:
引用直接在函数体声明,调用方直接调用,无需使用&符号。对调用方无感知。
这个设计从某些情况下看,其实也有一定的弊端:
- 传引用和传值无感知,意味着调用方通常会默认有返回值的话,是传值。没有返回值的时候传的引用。
- 调用方可能只是期待使用你的函数返回值,而你却偷偷地把入参指向的变量给修改了。
所以,我很讨厌一个函数,既修改了引用传递的入参,又有返回值
传引用破坏了函数的结构
传引用破坏了函数的结构,使函数与外部环境相互依赖,相互影响。
<?php
function foo(&$v){
co::sleep(rand(1,3));
echo "hello:".$v.PHP_EOL;
}
$arr = [1,2,3,4,5];
foreach($arr as $val){
go('foo',$val);
}
测试了一下,这点在PHP中体现不出来。但是在某些语言中,若在foo运行过程中,主函数修改了引用变量的值,可能导致foo函数的结果不可预期。
例如:
image.png
image.png
当然Go语言的函数传参没有引用传递。这里只是做个示范,一个未和外部环境隔离的函数,在并发编程的模式下的结果总是不可预期的。
总而言之,我觉得函数方法,不应该依赖和影响外部变量。so ,慎用引用传参
网友评论