php语言 百分网手机站

解析php中的foreach问题

时间:2020-11-12 15:09:00 php语言 我要投稿

解析php中的foreach问题

  php4中引入了foreach结构,这是一种遍历数组的简单方式。相比传统的for循环,foreach能够更加便捷的获取键值对。在php5之 前,foreach仅能用于数组;php5之后,利用foreach还能遍历对象。以下是小编为大家搜索整理的解析php中的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();

  //继续运行用户代码

  ……

  }

  根据上述理论,现在我们重新来分析下第一个foreach:

  第1遍循环,由于$v是一个引用,因此$v = &$arr[0],$v=$v*2相当于$arr[0]*2,因此$arr变成2,2,3

  第2遍循环,$v = &$arr[1],$arr变成2,4,3

  第3遍循环,$v = &$arr[2],$arr变成2,4,6

  随后代码进入了第二个foreach:

  第1遍循环,隐含操作$v=$arr[0]被触发,由于此时$v仍然是$arr[2]的引用,即相当于$arr[2]=$arr[0],$arr变成2,4,2

  第2遍循环,$v=$arr[1],即$arr[2]=$arr[1],$arr变成2,4,4

  第3遍循环,$v=$arr[2],即$arr[2]=$arr[2],$arr变成2,4,4

  OK,分析完毕。

  如何解决类似问题呢?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:

  我们从第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);

  // CV数组中创建出$arr**指针

  zval **variable_ptr_ptr = _get_zval_ptr_ptr_cv(&opline->op1, EX(Ts), BP_VAR_W TSRMLS_CC);

  if (IS_CV == IS_VAR && !variable_ptr_ptr) {

  ……

  }

  else {

  // 将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缓存了起来。