深入解析php中的foreach問題_PHP教程
推薦:PHP做好防盜鏈的設(shè)置方法盜鏈 是指服務(wù)提供商自己不提供服務(wù)的內(nèi)容,通過技術(shù)手段繞過其它有利益的最終用戶界面(如廣告),直接在自己的 網(wǎng)站上向最終用戶提供其它服務(wù)提供商的服務(wù)內(nèi)容,騙取最終用戶的瀏覽和點(diǎn)擊率。受益者不提供資源或提供很少的資源,而真正的服務(wù)提供商卻得不到任何的收 益
php4中引入了foreach結(jié)構(gòu),這是一種遍歷數(shù)組的簡單方式。相比傳統(tǒng)的for循環(huán),foreach能夠更加便捷的獲取鍵值對。在php5之 前,foreach僅能用于數(shù)組;php5之后,利用foreach還能遍歷對象(詳見:遍歷對象)。本文中僅討論遍歷數(shù)組的情況。
foreach雖然簡單,不過它可能會出現(xiàn)一些意外的行為,特別是代碼涉及引用的情況下。
下面列舉了幾種case,有助于我們進(jìn)一步認(rèn)清foreach的本質(zhì)。
問題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";
}
先從簡單的開始,如果我們嘗試運(yùn)行上述代碼,就會發(fā)現(xiàn)最后輸出為0=>2 1=>4 2=>4 。
為何不是0=>2 1=>4 2=>6 ?
其實(shí),我們可以認(rèn)為 foreach($arr as $k => $v) 結(jié)構(gòu)隱含了如下操作,分別將數(shù)組當(dāng)前的'鍵'和當(dāng)前的'值'賦給變量$k和$v。具體展開形如:
foreach($arr as $k => $v){
//在用戶代碼執(zhí)行之前隱含了2個(gè)賦值操作
$v = currentVal();
$k = currentKey();
//繼續(xù)運(yùn)行用戶代碼
……
}
根據(jù)上述理論,現(xiàn)在我們重新來分析下第一個(gè)foreach:
第1遍循環(huán),由于$v是一個(gè)引用,因此$v = &$arr[0],$v=$v*2相當(dāng)于$arr[0]*2,因此$arr變成2,2,3
第2遍循環(huán),$v = &$arr[1],$arr變成2,4,3
第3遍循環(huán),$v = &$arr[2],$arr變成2,4,6
隨后代碼進(jìn)入了第二個(gè)foreach:
第1遍循環(huán),隱含操作$v=$arr[0]被觸發(fā),由于此時(shí)$v仍然是$arr[2]的引用,即相當(dāng)于$arr[2]=$arr[0],$arr變成2,4,2
第2遍循環(huán),$v=$arr[1],即$arr[2]=$arr[1],$arr變成2,4,4
第3遍循環(huán),$v=$arr[2],即$arr[2]=$arr[2],$arr變成2,4,4
OK,分析完畢。
如何解決類似問題呢?php手冊上有一段提醒:
Warning : 數(shù)組最后一個(gè)元素的 $value 引用在 foreach 循環(huán)之后仍會保留。建議使用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
從這個(gè)問題中我們可以看出,引用很有可能會伴隨副作用。如果不希望無意識的修改導(dǎo)致數(shù)組內(nèi)容變更,最好及時(shí)unset掉這些引用。
問題2:
$arr = array('a','b','c');
foreach($arr as $k => $v) {
echo key($arr), "=>", current($arr);
}
// 打印 1=>b 1=>b 1=>b
這個(gè)問題更加詭異。按照手冊的說法,key和current分別是取數(shù)組中當(dāng)前元素的的鍵值。
那為何key($arr)一直是1,current($arr)一直是b呢?
先用vld查看編譯之后的opcode:

我們從第3行的ASSIGN指令看起,它代表將array('a','b','c')賦值給$arr。
由 于$arr為CV,array('a','b','c')為TMP,因此ASSIGN指令找到實(shí)際執(zhí)行的函數(shù)為 ZEND_ASSIGN_SPEC_CV_TMP_HANDLER。這里需要特別指出,CV是PHP5.1之后才增加的一種變量cache,它采用數(shù)組的 形式來保存zval**,被cache住的變量再次使用時(shí)無需去查找active符號表,而是直接去CV數(shù)組中獲取,由于數(shù)組訪問速度遠(yuǎn)超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數(shù)組中創(chuàng)建出$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數(shù)組中被加入zval**指針,指針指向?qū)嶋H的array,這表示$arr已經(jīng)被CV緩存了起來。
接下來執(zhí)行數(shù)組的循環(huán)操作,我們來看FE_RESET指令,它對應(yīng)的執(zhí)行函數(shù)為ZEND_FE_RESET_SPEC_CV_HANDLER:
static int ZEND_FASTCALL ZEND_FE_RESET_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
……
if (……) {
……
} else {
// 通過CV數(shù)組獲取指向array的指針
array_ptr = _get_zval_ptr_cv(&opline->op1, EX(Ts), BP_VAR_R TSRMLS_CC);
……
}
……
// 將指向array的指針保存到zend_execute_data->Ts中(Ts用于存放代碼執(zhí)行期的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) {
// 重置數(shù)組內(nèi)部指針
zend_hash_internal_pointer_reset(fe_ht);
if (ce) {
……
}
is_empty = zend_hash_has_more_elements(fe_ht) != SUCCESS;
// 設(shè)置EX_T(opline->result.u.var).fe.fe_pos用于保存數(shù)組內(nèi)部指針
zend_hash_get_pointer(fe_ht, &EX_T(opline->result.u.var).fe.fe_pos);
} else {
……
}
……
}
分享:如何使用PHP實(shí)現(xiàn)javascript的escape和unescape函數(shù)前端開發(fā)工程師都知道javascript有編碼函數(shù)escape()和對應(yīng)的解碼函數(shù)unescape(),而php中只有個(gè)urlencode和 urldecode,這個(gè)編碼和解碼函數(shù)對encodeURI和encodeURIComponent有效,但是對escape的是無效的。 javascript中的escape()函數(shù)和unescape()函數(shù)用戶字符串編碼
- PHPNOW安裝Memcached擴(kuò)展方法詳解
- php記錄頁面代碼執(zhí)行時(shí)間
- PHP中獎(jiǎng)概率的抽獎(jiǎng)算法程序代碼
- apache設(shè)置靜態(tài)文件緩存方法介紹
- php對圖像的各種處理函數(shù)代碼小結(jié)
- PHP 關(guān)于訪問控制的和運(yùn)算符優(yōu)先級介紹
- 關(guān)于PHP語言構(gòu)造器介紹
- php/js獲取客戶端mac地址的實(shí)現(xiàn)代碼
- php5.5新數(shù)組函數(shù)array_column使用
- PHP preg_match的匹配多國語言的技巧
- php 中序列化和json使用介紹
- php采集文章中的圖片獲取替換到本地
- 相關(guān)鏈接:
- 教程說明:
PHP教程-深入解析php中的foreach問題
。