Позвольте мне префикс этого, сказав, что я знаю, что foreach
есть, и как его использовать. Этот вопрос касается того, как он работает под капотом, и мне не нужны ответы по строкам "это то, как вы зацикливаете массив с помощью foreach
".
Долгое время я предполагал, что foreach
работал с самим массивом. Затем я нашел много ссылок на то, что он работает с копией массива, и с тех пор я полагаю, что это конец истории. Но я недавно занялся обсуждением этого вопроса, и после небольшого эксперимента выяснилось, что это на самом деле не на 100%.
Позвольте мне показать, что я имею в виду. Для следующих тестовых примеров мы будем работать со следующим массивом:
$array = array(1, 2, 3, 4, 5);
foreach ($array as $item) {
echo "$item\n";
$array[] = $item;
}
print_r($array);
/* Output in loop: 1 2 3 4 5
$array after loop: 1 2 3 4 5 1 2 3 4 5 */
Это ясно показывает, что мы не работаем напрямую с исходным массивом - иначе цикл будет продолжаться вечно, так как мы постоянно нажимаем элементы на массив во время цикла. Но просто чтобы убедиться, что это так:
foreach ($array as $key => $item) {
$array[$key + 1] = $item + 2;
echo "$item\n";
}
print_r($array);
/* Output in loop: 1 2 3 4 5
$array after loop: 1 3 4 5 6 7 */
Это подтверждает наш первоначальный вывод, мы работаем с копией исходного массива во время цикла, иначе мы увидим измененные значения во время цикла. Но...
Если мы посмотрим в manual, мы найдем это утверждение:
При первом запуске foreach внутренний указатель массива автоматически reset к первому элементу массива.
Правильно... это, по-видимому, предполагает, что foreach
полагается на указатель массива исходного массива. Но мы только что доказали, что мы не работаем с исходным массивом, не так ли? Ну, не совсем.
// Move the array pointer on one to make sure it doesn't affect the loop
var_dump(each($array));
foreach ($array as $item) {
echo "$item\n";
}
var_dump(each($array));
/* Output
array(4) {
[1]=>
int(1)
["value"]=>
int(1)
[0]=>
int(0)
["key"]=>
int(0)
}
1
2
3
4
5
bool(false)
*/
Таким образом, несмотря на то, что мы не работаем напрямую с исходным массивом, мы работаем напрямую с указателем исходного массива - тот факт, что указатель находится в конце массива в конце цикла, показывает это. Кроме того, это не может быть правдой - если бы это было так, то test case 1 зацикливался бы навсегда.
В руководстве по PHP также указано:
Поскольку foreach полагается на указатель внутреннего массива, изменяя его в цикле, может привести к неожиданному поведению.
Хорошо, давайте узнаем, что такое "неожиданное поведение" (технически, любое поведение неожиданно, поскольку я больше не знаю, чего ожидать).
foreach ($array as $key => $item) {
echo "$item\n";
each($array);
}
/* Output: 1 2 3 4 5 */
foreach ($array as $key => $item) {
echo "$item\n";
reset($array);
}
/* Output: 1 2 3 4 5 */
... ничего неожиданного там, на самом деле, похоже, поддерживает теорию "копий источника".
Вопрос
Что здесь происходит? Мой C-fu недостаточно хорош для того, чтобы я мог извлечь правильный вывод, просто взглянув на исходный код PHP, я был бы признателен, если бы кто-то мог перевести его на английский для меня.
Мне кажется, что foreach
работает с копией массива, но устанавливает указатель массива исходного массива в конец массива после цикла.
- Это правильно и вся история?
- Если нет, что это на самом деле делает?
- Есть ли ситуация, когда использование функций, которые настраивают указатель массива (
each()
,reset()
и др.) во времяforeach
, может повлиять на результат цикла?