当前位置: 首页 > 后端技术 > PHP

PHP澄清foreach的潜规则

时间:2023-03-29 15:32:37 PHP

原文地址:https://www.hongweipeng.com/i...很长一段时间,我以为foreach在循环时使用了原始数组的副本。但最近经过一些实验后发现这不是100%正确的。比如copy的语句是有道理的:$array=array(1,2,3,4,5);foreach($arrayas$item){echo"$item\n";$数组[]=$项目;}print_r($array);/*Outputinloop:12345$arrayafterloop:1234512345*/在这个例子中,修改循环体中的数组不影响循环过程中,copy语句是有道理的。然而$arr=[1,2,3,4,5];$obj=[6,7,8,9,10];$ref=&$arr;foreach($refas$val){echo$val;如果($val==3){$ref=$obj;}}//在php5.x中输出:123678910//在php7.x中输出:12345对于不同的PHP版本,输出会有所不同,php7提到foreach,foreach有3个变化:foreach不再改变内部数组指针;foreach按值遍历时,操作的值是数组的一个副本;foreach通过引用遍历时,具有更好的迭代特性。因此,在讨论foreach中的数组复制问题时,不得不分版本进行说明。在这里,https://stackoverflow.com/que...有更详细的描述,并给出了大多数情况的例子。本文做一些整理和总结。copy-on-write操作之所以和预期的不一样和不一样,一部分是因为触发了copy-on-write,另一部分是foreach本身的机制。php底层有两个属性分别处理引用计数(refcount)和全引用计数(is_ref)。当类似$a=[1,2,3];创建并初始化后,对象的is_ref会被设置为0,refcount会被设置为1;当像$b=&$a这样通过引用传递时,is_ref和refcount都会是+1;当像$c=$a时,refcount将是+1。什么情况下会触发copy-on-write?当变量重新赋值$a=1;时,如果此时$a的is_ref=0且refcount>1,那么就会触发复制;否则,原始对象将被修改。$a=[1,2,3]$b=$a;$a[]=5;//$a的is_ref=0,refcount>1触发copy-on-write,那么$a和$b是两个不同的数组在什么情况下可以跳过copy-on-write直接对原数组进行操作呢?根据copy-on-write的触发规则,一个简单的skipmodification机制就是进行引用复制,使得is_ref>0。然后可以在迭代过程中进行修改:1。foreach对current的影响7.xforeach不会修改内部指针,所以讨论current影响的部分是指5.x版本。当前示例11,'EzFY'=>2,'FYEz'=>3];foreach($arrayas&$value){unset($array['EzFY']);$array['FYFY']=4;var_dump($value);}//outputin5.x:1,4//outputin7.x:1,3,4in5.x版本由于HashPointer回收机制,会直接跳转到新元素(这应该被认为是一个错误)。而且7.x版本不再依赖元素哈希,所以感觉7.x的行为更正确。在循环中替换被迭代的实体PHP允许在循环中替换被迭代的实体,所以对于原始数组的操作,它也会被替换为另一个数组,开始它的迭代。$arr=[1,2,3,4,5];$obj=(对象)[6,7,8,9,10];$ref=&$arr;foreach($refas$val){echo"$val\n";如果($val==3){$ref=$obj;}}//5.x中的输出:123678910//7.x中的输出:1,2,3,4,5按值传递,始终对副本进行操作,替换实体确实不行。虽然手术是允许的,但我想没有人会去做。ExtrainternalpointerandHashPointer为了引入指针回收的概念,我们可以从一个问题开始,如何只用一个内部数组指针同时满足两个循环://这里使用by-ref迭代来确保//两个循环中确实是同一个数组而不是copyforeach($arras&$v1){foreach($arras&$v){//...}}解决方法是复制当前的指针元素和指向的元素被保存。循环体运行后,如果元素仍然存在,则IAP恢复为之前保存的指针;如果该元素已被删除,IAP将使用当前位置。这个保存指针和元素的地方就是HashPointer。HashPointer备份恢复机制带来的便利是我们可以临时修改数组的指针:$array=[1,2,3,4,5];foreach($arrayas&$value){var_dump($value);reset($array);}//在5.x和7.x中输出:1,2,3,4,5如果你想干扰这个机制,你必须让它失败:$array=[1,2,3,4,5];$ref=&$array;foreach($arrayas$value){var_dump($value);取消设置($数组[1]);reset($array);}//5.x中的输出:1,1,3,4,5//7.x中的输出:1,2,3,4,5是按值传递的,操作为总是一个副本