• 售前

  • 售后

热门帖子
入门百科

为何说PHP引用是个坑,要慎用

[复制链接]
春宵一刻你懂刻z 显示全部楼层 发表于 2021-10-25 19:34:21 |阅读模式 打印 上一主题 下一主题
前言
去年我加入了许多次集会,此中八次集会里我进行了相关发言,这此中我多次谈到了 PHP 的引用标题,因为许多人对它的明白有所偏差。在深入讨论这个标题之前,我们先回顾一下引用的根本概念,明确什么是“引用传递”。
在 PHP 中引用意味着用差异的名字访问同一个变量内容,岂论你用哪个名字对变量做出了运算,其他名字访问的内容也将改变。
让我们通过代码来加深对此的明白。 起首我们写几个简单的语句,把一个变量赋值给另一个变量,而且改变另一个变量:
  1. <?php
  2. $a = 23;
  3. $b = $a;
  4. $b = 42;
  5. var_dump($a); // int(23)
  6. var_dump($b); // int(42)
复制代码
这个脚本显示 $a 值仍然为 23  ,而 $b 则即是 42 。出现这个情况的缘故起因是我们得到的是一个拷贝(具体发生了什么稍后解说。。。)现在我们使用引用来做同样的事情:
  1. <?php
  2. $a = 23;
  3. $b = &$a;
  4. $b = 42;
  5. var_dump($a); // int(42)
  6. var_dump($b); // int(42)
  7. ?>
复制代码
现在 $a 的值也改酿成了 42 。 究竟上,$a 和 $b 之间没有任何区别,它们都使用了同一个变量容器(又名: zval )。 将这两者分开的唯一方法是使用 unset() 函数销毁此中任何一个变量。
在 PHP 中,引用不但能用在平常语句中,还能用于函数参数和返回值:
  1. <?php
  2. function &foo(&$param) {
  3. $param = 42;
  4. return $param;
  5. }
  6. $a = 23;
  7. echo "\$a before calling foo(): $a\n";
  8. $b = foo($a);
  9. echo "\$a after the call to foo(): $a\n";
  10. $b = 23;
  11. echo "\$a after touching the returned variable: $a\n";
  12. ?>
复制代码
你认为上面的效果是什么呢?—— 没错,就像下面如许:
  1. $a before calling foo(): 23
  2. $a after the call to foo(): 42
  3. $a after touching the returned variable: 42
复制代码
这里我们初始化了一个变量,并把它作为一个引用参数传给了一个函数。函数改变了它,它有了新值。该函数返回同一个变量,我们更改了返回的变量和它的原始值。。。 等等!它没变,不是吗!? —— 没错,可引用就是如许。 具体发生了如下事情:该函数返回了一个引用,引用了 $a 的变量容器 zval,而且通过 = 赋值操纵符为它创建了一个副本。
为了修复这个标题,我们必要添加一个额外的 & 操纵符:
  1. $b = &foo($a);
复制代码
效果和我们所期望的一样:
  1. $a before calling foo(): 23
  2. $a after the call to foo(): 42
  3. $a after touching the returned value: 23
复制代码
总结一下: PHP 的引用就是同一个变量的别名,想要正确的使用它们可能很难。想要具体了解引用计数,这里有份根本资料,请参阅 手册中的引用计数根本知识 。
PHP 5 发布时最大的变动是『对象处置处罚方式』。一般我们明白为:
  1. 在 PHP 4 中,对象被当成变量来对待,所以当对象作为函数传参时,他们是被复制的。但在 PHP 5 中,他们永远是『引用传参』。
复制代码
以上的明白并不完全正确。其主要目的是遵循『面对对象模式』:对象传参给函数大概方法后,这个函数发送一个指令给对象(比方调用了一个方法)以此来改变对象的状态(比方对象的属性)。因此传参进去的对象必须为同一个。 PHP 4 的面对对象用户使用『引用传参』来解决这个标题,不外很难做到完美。PHP 5 引进了独立于变量容器的『对象存储器』。当一个对象赋值给变量时,变量不再存储整个对象(属性表和其他的『类』信息),而是存储这个对象所在 存储器的引用 —— 当我们复制一个对象变量时,我们复制的是这个『存储器的引用』。这很容易被误解为『引用』,但是『存储器的引用』与『引用』是完全差异的概念。下面的示例代码有助于我们更好地区分:
  1. <?php
  2. // 创建一个对象和此对象的引用变量
  3. $a = new stdclass;
  4. $b = $a;
  5. $c = &$a;
  6. // 对『对象』进行操作
  7. $a->foo = 42;
  8. var_dump($a->foo); // int(42)
  9. var_dump($b->foo); // int(42)
  10. var_dump($c->foo); // int(42)
  11. // 现在直接改变变量的类型
  12. $a = 42;
  13. var_dump($a); // int(42)
  14. var_dump($b); // object(stdClass)#1719 (1) {
  15.     //   ["foo"]=>
  16.     //   int(42)
  17.     // }
  18. var_dump($c); // int(42)
  19. ?>
复制代码
以上代码中,修改对象的属性会影响到 复制 的变量 $b 和引用的变量 $c。但是在末了区块的代码中,当我们修改 $a 的类型时,引用的 $c 发生了变化,而复制得到的变量 $b 不会发生改变,这是个大多数有面对对象经验的工程师所等候的。

So, 面对对象是唯一使用『引用』的来由,但是现在 PHP 4 已死,你也可以放弃此类用法了。
另一个人们使用『引用』的来由是 —— 这将让代码更快。但是这是错误的,引用并不会使代码实行速度变快,更糟糕的是,许多时候『引用』会让你的代码实行效率更低。
我必须再谨慎强调一次:是的,许多时候『引用』会让你的代码实行效率更低。
别的语言的工程师,他们阅读别的语言编码规范,会看到发起在处置处罚大的数据布局大概字串时,使用指针来减小对内存的消耗以进步运行效率。这些工程师误将此概念明白到『引用』上,然而『指针』与『引用』是完全差异的技能模型。PHP 解析器与其他语言差异,在 PHP 中,我们使用『写时复制(copy-on-write)』模型。
在『写时复制』模型里,赋值和函数传参不会触发 复制 动作,你可以明白为多个差异的变量指向同一个『变量容器』,只有当『写』动作发生时,才会触发复制动作。这意味着,即使变量看起来像是『复制』的,本质上却不是。所以当传参一个巨大的变量给某个函数时,并不会对性能造成多大影响。不外此时如果你使用引用传参的话,引用传参会关闭『写时复制』机制,这会导致接下来那些没有使用引用的变量传参会被立即复制一份。这也不是天下末日,你也可以在全部地方都引用就行了嘛。究竟并非如此:PHP 的内部机制依靠于『写时复制』模型,存在许多你无法修改的内部函数传参。
我曾在某处看到过雷同下面如许的代码:
  1. <?php
  2. function foo(&$data) {
  3. for ($i = 0; $i < strlen($data); $i++) {
  4.   do_something($data{$i});
  5. }
  6. }
  7. $string = "... looooong string with lots of data .....";
  8. foo(string);
  9. ?>
复制代码
显然,上面这段代码的第一个标题是:在循环中调用 strlen() 而不是使用已经计算好的长度。也就是说调用一次 strlen($data) 就可以了的,但是他却调用了许多次。 差异于 C 这类语言, 一般来说,PHP 的字符串都自带了长度,因此也不用进行长度的计算。所以就 strlen() 而言,这还不算太糟糕。 但现在另一个标题是,案例中的这个开发者为了节流时间,传递了一个引用作为参数以显示本身的聪明。 然而,strlen() 期望得到的是一个副本。『写时复制』不能用于引用,因此 $data 将会在 strlen() 调用时被复制,strlen() 将会做一个绝对简单的操纵 —— 究竟上 strlen() 原来就是 PHP 里最简单的函数之一 —— 紧接着该副本就会被直接销毁。
如果没有使用引用,也就没须要进行复制操纵,代码实行也会更快。而且就算 strlen() 支持引用,你也不会因此得到更多好处。
总的来说:
      
  • 除了 PHP4 的遗留标题,不要在面向对象(OO)中使用引用。  
  • 不要使用引用来提升性能。
使用引用来完成事情的第三个标题是:通过参数的引用来返回数据所导致的糟糕的 API 计划。这个标题照旧因为谁人开发者没有意识到『PHP 就是 PHP 而不是其他语言』所导致的。
在 PHP 中,同一个函数可以返回差异数据类型。—— 因此,你可以在函数实行成功时返回一个字符串,而在失败时返回一个布尔值 false,PHP 也允许返回复杂的布局类型,比如数组和对象。所以在必要返回许多东西的时候,可以将他们打包在一起。另外,非常也是函数返回的一种方式。
使用引用是一件欠好的事情,除了引用本身欠好,而且还会使性能降落这个究竟外,使用引用这种方式会使得代码难以维护。像下面这段代码的函数调用:
  1. do_something($var);
复制代码
你盼望 $var 发生改变吗?—— 固然不会。然而,如果 do_something() 传递的参数是引用,它就可能会改变。
这类 API 的另一个标题是:函数不能链式调用,因而你总会碰到必须使用临时变量的场景。链式调用可能会使可读性低落,但是在许多场景下,链式调用使得代码更加简便。
关于引用的糟糕的计划决定,我个人最喜欢的一个例子是 PHP 自带的 sort() 函数。sort() 使用一个数组作为引用参数,然后通过引用返回一个排好序的数组。 像通例那样通过值返回一个排好序的数组可能还更好些。固然,这么做是由于汗青的缘故起因:sort() 比『写时复制』更早出现。『写时复制』产生于 PHP4,而 sort() 则更早,它早在 PHP 照旧作为一种在 Web 上做起事来很方便的东西,而不是真正的成为本身的语言的时候就存在了。
总之: 在 PHP 中,引用是欠好的。 不要使用引用。 它们只会惹事生非,另外,不要对使用引用来提升引擎抱有盼望。
总结
以上就是这篇文章的全部内容了,盼望本文的内容对各人的学习大概工作具有一定的参考学习代价,如果有疑问各人可以留言交换,谢谢各人对脚本之家的支持。

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作