美文网首页
真的要用引用传参吗?

真的要用引用传参吗?

作者: 刀斧手何在 | 来源:发表于2020-01-24 21:49 被阅读0次

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

基础知识


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

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

引用变量

<?php
$a = range(0,100);
$b = $a;

此时a 与b 指向的是同一块内存空间吗?是的。
PHP有一个机制叫做COW机制,也就是Copy On write 。只有a 或b 发生写操作时,才会开辟一块新的空间,a和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 ,慎用引用传参

相关文章

  • 真的要用引用传参吗?

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

  • C++---CHAPTER 6: FUNCTION

    参数传递 传值调用 指针形参: 2.传引用调用 如果函数无需改变引用形参的值,最好将其声明为常量引用。 const...

  • c指针和引用和函数指针

    引用:引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。 指针传参 引用传参 函数指针

  • Python共享传参

    函数的参数作为引用 Python 唯一支持的参数传递是 共享传参 ,也就是常说的引用传参。函数内部的形参是实参的别...

  • 一张图解决java传值传引用问题

    分析此类问题只需要知道两点:java有两种传参方式,基本类型用传值,包装类型用传引用无论是传值还是传引用,形参都是...

  • 一张图解决java传值传引用问题

    分析此类问题只需要知道两点:java有两种传参方式,基本类型用传值,包装类型用传引用无论是传值还是传引用,形参都是...

  • VUE路由传参和接收

    params 与 query传参,query要用path来引入,params要用name来引入 //params传...

  • Python参数传递,既不是传值也不是传引用

    面试的时候,有没有被问到Python传参是传引用还是传值这种问题?有没有听到过Python传参既不是传值也不是传引...

  • Shell 基础语法

    判断 分支 循环 判断标识 函数传参 引用其他脚本

  • java引用 VS C++指针小结

    java参数传参都是传引用,其实就是指针,但是java只能传递一级指针 比如上方,test(str);虽然把引用传...

网友评论

      本文标题:真的要用引用传参吗?

      本文链接:https://www.haomeiwen.com/subject/ifncthtx.html