深入解析php中的foreach問題_PHP教程

      編輯Tag賺U幣
      教程Tag:暫無Tag,歡迎添加,賺取U幣!

      推薦:PHP做好防盜鏈的設置方法
      盜鏈 是指服務提供商自己不提供服務的內容,通過技術手段繞過其它有利益的最終用戶界面(如廣告),直接在自己的 網站上向最終用戶提供其它服務提供商的服務內容,騙取最終用戶的瀏覽和點擊率。受益者不提供資源或提供很少的資源,而真正的服務提供商卻得不到任何的收 益

      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();
      //繼續運行用戶代碼
      ……
      }


      根據上述理論,現在我們重新來分析下第一個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:

       

      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);

      // 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緩存了起來。02.png

      接下來執行數組的循環操作,我們來看FE_RESET指令,它對應的執行函數為ZEND_FE_RESET_SPEC_CV_HANDLER:

      復制代碼 代碼如下:
      static int ZEND_FASTCALL ZEND_FE_RESET_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
      {
      ……
      if (……) {
      ……
      } 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);
      PZVAL_LOCK(array_ptr);
      if (iter) {
      ……
      } else if ((fe_ht = HASH_OF(array_ptr)) != NULL) {
      // 重置數組內部指針
      zend_hash_internal_pointer_reset(fe_ht);
      if (ce) {
      ……
      }
      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 {
      ……
      }
      ……
      }

      分享:如何使用PHP實現javascript的escape和unescape函數
      前端開發工程師都知道javascript有編碼函數escape()和對應的解碼函數unescape(),而php中只有個urlencode和 urldecode,這個編碼和解碼函數對encodeURI和encodeURIComponent有效,但是對escape的是無效的。 javascript中的escape()函數和unescape()函數用戶字符串編碼

      共4頁上一頁1234下一頁
      來源:模板無憂//所屬分類:PHP教程/更新時間:2013-07-02
      相關PHP教程