一、next()到底动了什么
PHP每个数组内部都维护着一个指针,指向“当前元素”。这个指针不是给人看的,是给一批内部函数用的,比如current()、next()、prev()、reset()、end()、key()。
next()做的事情就一句话:把指针往后挪一格,然后返回挪过去之后那个元素的值。
$company = array("java", "T", "point", "coding", "every language");
echo next($company);
输出是T。
这里有个细节值得注意:数组刚创建时,内部指针默认停在第一个元素java上。next()先把指针从java移到T,再返回T的值。它不是返回“当前元素的下一个”,而是“移动后指针所在的那个”。
函数原型:
mixed next(array &$array);
参数是引用传递,必须传变量。返回值类型是mixed——有下一个元素就返回它的值,没有就返回false。
二、返回false这件事,比想象中麻烦
如果数组里有个元素的值本身就是false、0、""或null,next()返回的时候你分不清是“到头了”还是“值就是false”。
$data = array("code1", false, "code3");
echo next($data); // false
echo next($data); // code3
第一次next()返回false,但指针其实正常移到了第二个元素,只是那个元素的值是false。你要判断是否真的走到末尾,不能只看返回值,得配合key()或current()。
我早期写过一个遍历逻辑,用while(($val=next($arr))!==false)来判断结束,结果数组里一旦有falsy值就提前跳出。后来改成用key()判断:
while (key($arr) !== null) {
$val = current($arr);
// 处理
next($arr);
}
key()在指针越界后返回null,这个判断比!==false靠谱得多。
三、和current()、prev()配合看指针行为
单独看next()容易糊涂,把几个函数放一起就清楚了。
$subject = array("java", "c", "os", "compiler", "dbms");
echo current($subject), "\n"; // java
echo next($subject), "\n"; // c
echo next($subject), "\n"; // os
current()读的是指针当前所在的值,不移动指针。next()读的是移动之后的值。两者搭配就是“读一次、走一步”的节奏。
再看prev()加入后的情况:
$social = array("", "fb", "quora", "telegram", "whatsapp");
echo current($social), "\n"; //
echo next($social), "\n"; // fb
echo prev($social), "\n"; //
echo next($social), "\n"; // fb
prev()把指针往回挪一格并返回那个值。所以next再prev就回到了原位,再next又前进一格。
这里有个边界:如果指针已经在第一个元素,prev()会返回false,但指针不会跑到数组外面去,它还是停在第一个元素。同理,指针在之后一个元素时调next(),返回false,指针停在之后一个元素,不会越界。这个设计让指针在两端是“卡住”的,不会因为多调几次就跑飞。
四、next()和foreach的关系
很多人以为foreach也用内部指针,其实不是。foreach在PHP7之后操作的是数组的一个副本(写时复制机制),它有自己的遍历逻辑,不碰内部指针。
$arr = array("code1", "code2", "code3");
foreach ($arr as $v) {
echo next($arr), "\n";
}
这段代码里next()操作的是原数组的指针,foreach走的是另一套。两者互不干扰,输出可能会让你意外。别在foreach里混用next(),逻辑会变得很难读。
那什么时候用next()?我的经验是:需要手动、有条件地逐步遍历数组时用它。比如解析一个扁平的token列表,遇到某个标记就跳过下一条,或者要回退一步重新读。这种“带状态的顺序访问”用next()和prev()比foreach灵活。普通遍历还是foreach更清楚。
五、一组完整的指针函数
和next()一起用的还有几个,放在一起记:
| 函数 | 作用 | 指针是否移动 |
|---|---|---|
| current() | 返回当前元素值 | 否 |
| key() | 返回当前元素键 | 否 |
| next() | 指针前移一位,返回新位置的值 | 是 |
| prev() | 指针后移一位,返回新位置的值 | 是 |
| reset() | 指针回到第一个元素,返回第一个元素值 | 是 |
| end() | 指针移到之后一个元素,返回之后一个元素值 | 是 |
| each() | 返回当前键值对并前移指针 | 是(PHP7.2起废弃,8.0移除) |
each()已经不能用了,老代码里看到要换掉。替代方式就是current()+key()+next()三件套。
reset()在遍历开始前很有用。如果数组之前被next()动过,指针停在中间,直接遍历会从中间开始。加一句reset($arr)回到开头。
六、本节课程知识要点
-
next()的作用是前移内部指针并返回新位置的值,不是返回“当前元素的下一个值”。
-
返回值是mixed,没有更多元素时返回false,但元素值本身可能是false,判断结束要用key()配合。
-
参数是引用传递,只能传变量。
-
指针到两端会卡住,不会越界,多次调用next()或prev()不会出错。
-
foreach不使用内部指针,不要和next()混用。
-
each()已在PHP8.0移除,老代码里的each()要替换成current()/key()/next()组合。
-
reset()是遍历前的保险动作,确保指针从第一个元素开始。
七、几个实际项目里的用法和坑
解析配置行。我做过一个读取自定义配置格式的小工具,每行是一个指令,有的指令带参数占下一行。这时候用next()读下一行很自然:
$lines = file("config.txt", FILE_IGNORE_NEW_LINES);
reset($lines);
while (($line = current($lines)) !== false) {
if (strpos($line, "include") === 0) {
$nextLine = next($lines); // 读参数行
}
next($lines);
}
这里要小心:current($lines)!==false在行内容为空字符串时也会跳出,因为""!==false其实成立……等等,空字符串不等于false,这个判断是安全的。但如果某行内容恰好是"0","0"!==false也成立。真正危险的是用!=而不是!==。用严格不等于,可以避开大部分falsy值问题。
不要依赖next()做分页。有人把结果集当数组,用next()逐条取,取到false就认为结束。数据里只要有falsy值就出错。分页还是用索引或专门的游标变量,别把内部指针当业务状态用。
调试时注意指针位置。在函数里对传入的数组调了next(),如果参数是引用传递,调用方的数组指针也会被改。函数内部改指针这种事不容易被发现,排查起来很费时间。要么在函数开头reset(),要么避免在函数里操作传入数组的内部指针。
个人建议:日常遍历用foreach,需要回退或有条件跳步时再用next()/prev()。把内部指针当成一种“低级工具”,用的时候心里清楚它在哪,别让它隐式地影响别处的代码。
next()本身不复杂,复杂的是它牵扯的“数组内部指针”这个隐式状态。用对了,它能在顺序访问的场景里提供灵活控制;用错了,falsy值和跨函数指针污染会带来很难查的bug。记住三点:判断结束用key(),遍历前reset(),别和foreach混着用。