php的数组都有一个内部指针,指向数组的元素,初始化的时候是第一个,我要便利数组,让内部指针逐个移动
$arr = array ('a', 'b', 'c', 'd', 'e');
foreach ($arr as $k => $v) {
$curr = current($arr);
echo "{$k} => {$v} -- {$curr}\n";
}
得到结果是
0 => a -- b 1 => b -- b 2 => c -- b 3 => d -- b 4 => e -- b
内部指针向后移动了一位就再也没动过了。。。
foreach对这个数组做了什么呢?为什么呢?
我要让指针遍历数组,得到如下结果改怎么做呢?
0 => a -- a 1 => b -- b 2 => c -- c 3 => d -- d 4 => e -- e
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号
这个问题我在bugs.php.net上回答过: https://bugs.php.net/bug.php?id=63752
1. at the beginning of foreach: ZEND_FE_RESET which increase the refoucnt of $a
2. then FE_FETCH, reset internal pointer of $a
3. then current, current declared to accept a reference, but $a is not a ref and refcount > 1 , then -> separation
PHP 的数组指针操作函数:
pos()http://cn2.php.net/manual/en/function...end()http://cn2.php.net/manual/en/function...prev()http://cn2.php.net/manual/en/function...next()http://cn2.php.net/manual/en/function...each()http://cn2.php.net/manual/en/function...reset()http://cn2.php.net/manual/en/function...current()http://cn2.php.net/manual/en/function...foreach()操作原始数组的一个拷贝,如果需要移动指针,使用while结构加上each()来实现。$arr = array ('a', 'b', 'c', 'd', 'e'); reset($arr); while (list($k, $v) = each($arr)) { # 当前指针已经被指向了下一位 $curr = current($arr); echo "{$k} => {$v} -- {$curr}\n"; }参见 http://cn2.php.net/manual/en/function... 的 example #2。
php的所有变量实际上是用一个struct zval来表示的。
/* Zend/zend.h */ typedef struct _zval_struct zval; typedef union _zvalue_value { long lval; /* long value */ double dval; /* double value */ struct { char *val; int len; } str; HashTable *ht; /* hash table value */ zend_object_value obj; } zvalue_value; struct _zval_struct { /* Variable information */ zvalue_value value; /* value */ zend_uint refcount; zend_uchar type; /* active type */ zend_uchar is_ref; };而数组就是其中的"HashTable *ht",实际上就是一个哈希表(Hash Table),表中的所有元素同时又组成一个双向链表,它的定义为:
/* Zend/zend_hash.h */ typedef struct _hashtable { uint nTableSize; uint nTableMask; uint nNumOfElements; ulong nNextFreeElement; Bucket *pInternalPointer; /* Used for element traversal */ Bucket *pListHead; Bucket *pListTail; Bucket **arBuckets; dtor_func_t pDestructor; zend_bool persistent; unsigned char nApplyCount; zend_bool bApplyProtection; #if ZEND_DEBUG int inconsistent; #endif } HashTable;这里有一个 Bucket *pInternalPointer ,就是被reset/current/next等函数用来遍历数组保存位置状态的。Bucket的实现如下,可以看到这就是个赤裸裸的链表节点。
typedef struct bucket { ulong h; /* Used for numeric indexing */ uint nKeyLength; void *pData; void *pDataPtr; struct bucket *pListNext; struct bucket *pListLast; struct bucket *pNext; struct bucket *pLast; char arKey[1]; /* Must be last element */ } Bucket;而foreach的实现,则位于 ./Zend/zend_compile.h ,在解释期被flex翻译成由 zend_do_foreach_begin zend_do_foreach_cont zend_do_foreach_end 这三个函数(以及相关代码)组合起来。由于看起来比较晦涩,我就不贴出来了(实际上我也没看太懂),详情可以参考雪候鸟的这篇:深入理解PHP原理之foreach
最后附一段php代码的opcode
<?php $arr = array(1,2,3); foreach ($arr as $x) echo $x; ?>number of ops: 12 compiled vars: !0 = $arr, !1 = $x line # * op return operands ----------------------------------------------------- 2 0 > INIT_ARRAY ~0 1 用1初始化数组 1 ADD_ARRAY_ELEMENT ~0 2 添加个2 2 ADD_ARRAY_ELEMENT ~0 3 添加个3 3 ASSIGN !0, ~0 存入$arr 3 4 > FE_RESET $2 !0, ->10 $2 = FE_RESET($arr), 失败则跳到#10 5 > > FE_FETCH $3 $2, ->10 $3 = FE_FETCH($2), 失败则跳到#10 6 > ZEND_OP_DATA 7 ASSIGN !1, $3 $x = $3 4 8 ECHO !1 echo $x 9 > JMP ->5 跳到#5 10 > SWITCH_FREE $2 释放$2 5 11 > RETURN 1 返回