解析php中的foreach問題

來源:文萃谷 3.11W

php4中引入了foreach結構,這是一種遍歷數組的簡單方式。相比傳統的for循環,foreach能夠更加便捷的獲取鍵值對。在php5之 前,foreach僅能用於數組;php5之後,利用foreach還能遍歷對象。以下是小編為大家搜索整理的解析php中的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->), value);

PZVAL_LOCK(value);

}

}

ZEND_VM_NEXT_OPCODE();

}

ASSIGN指令完成之後,CV數組中被加入zval**指針,指針指向實際的array,這表示$arr已經被CV緩存了起來。

熱門標籤