加入收藏 | 设为首页 | 会员中心 | 我要投稿 济源站长网 (https://www.0391zz.cn/)- 数据工具、数据仓库、行业智能、CDN、运营!
当前位置: 首页 > 站长学院 > PHP教程 > 正文

深度解析php中的foreach问题

发布时间:2022-07-22 10:24:02 所属栏目:PHP教程 来源:互联网
导读:php4中引入了foreach结构,这是一种遍历数组的简单方式。相比传统的for循环,foreach能够更加便捷的获取键值对。在php5之 前,foreach仅能用于数组;php5之后,利用foreach还能遍历对象(详见:遍历对象)。本文中仅讨论遍历数组的情况。 foreach虽然简单,
  php4中引入了foreach结构,这是一种遍历数组的简单方式。相比传统的for循环,foreach能够更加便捷的获取键值对。在php5之 前,foreach仅能用于数组;php5之后,利用foreach还能遍历对象(详见:遍历对象)。本文中仅讨论遍历数组的情况。
 
  foreach虽然简单,不过它可能会出现一些意外的行为,特别是代码涉及引用的情况下。
 
  下面列举了几种case,有助于我们进一步认清foreach的本质。
 
  问题1:
 
  复制代码 代码如下:
 
  $arr = array(1,2,3);
 
  foreach($arr as $k => &$v) {
 
  $v = $v * 2;
 
  }
 
  // now $arr is array(2, 4, 6)
 
  foreach($arr as $k => $v) {
 
  echo "$k", " => ", "$v";
 
  }
 
  先从简单的开始,如果我们尝试运行上述代码,就会发现最后输出为0=>2 1=>4 2=>4 。
 
  为何不是0=>2 1=>4 2=>6 ?
 
  其实,我们可以认为 foreach($arr as $k => $v) 结构隐含了如下操作,分别将数组当前的'键'和当前的'值'赋给变量$k和$v。具体展开形如:
 
  复制代码 代码如下:
 
  foreach($arr as $k => $v){
 
  //在用户代码执行之前隐含了2个赋值操作
 
  $v = currentVal();
 
  $k = currentKey();
 
  //继续运行用户代码
 
  ……
 
  }
 
  如何解决类似问题呢?php手册上有一段提醒:
 
  Warning : 数组最后一个元素的 $value 引用在 foreach 循环之后仍会保留。建议使用unset()来将其销毁。
 
  复制代码 代码如下:
 
  $arr = array(1,2,3);
 
  foreach($arr as $k => &$v) {
 
  $v = $v * 2;
 
  }
 
  unset($v);
 
  foreach($arr as $k => $v) {
 
  echo "$k", " => ", "$v";
 
  }
 
  // 输出 0=>2 1=>4 2=>6
 
  从这个问题中我们可以看出,引用很有可能会伴随副作用。如果不希望无意识的修改导致数组内容变更,最好及时unset掉这些引用。
 
  问题2:
 
  复制代码 代码如下:
 
  $arr = array('a','b','c');
 
  foreach($arr as $k => $v) {
 
  echo key($arr), "=>", current($arr);
 
  }
 
  // 打印 1=>b 1=>b 1=>b
 
  这个问题更加诡异。按照手册的说法,key和current分别是取数组中当前元素的的键值。
 
  那为何key($arr)一直是1,current($arr)一直是b呢?
 
  先用vld查看编译之后的opcode:
 
  01.png
 
  我们从第3行的ASSIGN指令看起,它代表将array('a','b','c')赋值给$arr。
 
  由 于$arr为CV,array('a','b','c')为TMP,因此ASSIGN指令找到实际执行的函数为 ZEND_ASSIGN_SPEC_CV_TMP_HANDLER。这里需要特别指出,CV是PHP5.1之后才增加的一种变量cache,它采用数组的 形式来保存zval**,被cache住的变量再次使用时无需去查找active符号表,而是直接去CV数组中获取,由于数组访问速度远超hash表,因 而可以提高效率。
 
  复制代码 代码如下:
 
  static int ZEND_FASTCALL ZEND_ASSIGN_SPEC_CV_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
 
  {
 
  zend_op *opline = EX(opline);
 
  zend_free_op free_op2;
 
  zval *value = _get_zval_ptr_tmp(&opline->op2, EX(Ts), &free_op2 TSRMLS_CC);
 
  // 将array赋值给$arr
 
  value = zend_assign_to_variable(variable_ptr_ptr, value, 1 TSRMLS_CC);
 
  if (!RETURN_VALUE_UNUSED(&opline->result)) {
 
  AI_SET_PTR(EX_T(opline->result.u.var).var, value);
 
  PZVAL_LOCK(value);
 
  }
 
  }
 
  ZEND_VM_NEXT_OPCODE();
 
  }
 
  ASSIGN指令完成之后,CV数组中被加入zval**指针,指针指向实际的array,这表示$arr已经被CV缓存了起来。02.png
 
  接下来执行数组的循环操作,我们来看FE_RESET指令,它对应的执行函数为ZEND_FE_RESET_SPEC_CV_HANDLER:
 
  复制代码 代码如下:
 
  static int ZEND_FASTCALL ZEND_FE_RESET_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)

  } else {
 
  // 通过CV数组获取指向array的指针
 
  array_ptr = _get_zval_ptr_cv(&opline->op1, EX(Ts), BP_VAR_R TSRMLS_CC);

  // 将指向array的指针保存到zend_execute_data->Ts中(Ts用于存放代码执行期的temp_variable)
 
  AI_SET_PTR(EX_T(opline->result.u.var).var, array_ptr);

  } else if ((fe_ht = HASH_OF(array_ptr)) != NULL) {
 
  // 重置数组内部指针
 
  zend_hash_internal_pointer_reset(fe_ht);

  is_empty = zend_hash_has_more_elements(fe_ht) != SUCCESS;
 
  // 设置EX_T(opline->result.u.var).fe.fe_pos用于保存数组内部指针
 
  zend_hash_get_pointer(fe_ht, &EX_T(opline->result.u.var).fe.fe_pos);
 
  } else {
 
  ……
 
  }
  ……
 
  }。
 

(编辑:济源站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读