国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > PHP > 正文

深入解析php中的foreach問題

2020-03-22 19:04:56
字體:
來源:轉載
供稿:網友
前言:
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 = html' target='_blank'>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緩存了起來。

接下來執行數組的循環操作,我們來看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);

鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯系我們修改或刪除,多謝。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 红河县| 南宁市| 嘉善县| 黑水县| 宜昌市| 武城县| 安溪县| 郯城县| 兴和县| 广东省| 靖江市| 崇信县| 米脂县| 恭城| 遂平县| 汕尾市| 井研县| 新乡县| 开平市| 达州市| 宾川县| 汕头市| 明星| 仪陇县| 巴楚县| 铜川市| 永丰县| 合作市| 德安县| 确山县| 天等县| 沙雅县| 固安县| 吴江市| 嘉善县| 博乐市| 虞城县| 湟中县| 湟中县| 于田县| 宁蒗|