真的要用引用传参吗?

最近在写Go语言,Go没有引用。然后给我了一些启发,就把17年的引用基础的笔记翻出来,和函数的值传递和引用传递合在一起,又水一帖。

基础知识


在PHP中引用意味着用不同名字访问同一个变量的内容

引用的本质是别名而不是指针

引用变量

此时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());

使用引用的话,就不会触发写时复制机制

unset()与引用

unset 只会取消引用不会销毁内存空间

zval 结构体

PHP的变量都是基于zval结构体的,也就是说zval是PHP变量的容器

对象与引用

在PHP中,对象默认的传值方式是引用而不是赋值。所以,不用加&。

name = 'fangle';
xdebug_debug_zval('p1');

foreach 与引用

 $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并没有行作用域变量

函数传参是传值还是传引用?


 $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中体现不出来。但是在某些语言中,若在foo运行过程中,主函数修改了引用变量的值,可能导致foo函数的结果不可预期。

例如:


image.png

image.png

当然Go语言的函数传参没有引用传递。这里只是做个示范,一个未和外部环境隔离的函数,在并发编程的模式下的结果总是不可预期的。

总而言之,我觉得函数方法,不应该依赖和影响外部变量。so ,慎用引用传参

你可能感兴趣的:(真的要用引用传参吗?)