PHP For 循环
The three-part for loop, foreach over arrays, and the by-reference trap.
for 的三段式
<?php
for ($i = 1; $i <= 3; $i++) {
echo "第 $i 次\n";
}
// 递减
for ($i = 3; $i >= 1; $i--) {
echo $i . ($i > 1 ? "," : "");
}
echo "\n";
// 多表达式
for ($i = 0, $j = 10; $i < $j; $i += 3, $j -= 3) {
echo "i=$i j=$j\n";
}第 1 次
第 2 次
第 3 次
3,2,1
i=0 j=10
i=3 j=7foreach 遍历数组
foreach walks arrays and objects without touching indexes — much safer than for.
<?php
$arr = ["a" => 1, "b" => 2, "c" => 3];
$pairs = [];
foreach ($arr as $k => $v) {
$pairs[] = "$k=$v";
}
echo implode(", ", $pairs), "\n";
// 只取值
foreach ([10, 20, 30] as $v) {
echo $v . " ";
}
echo "\n";a=1, b=2, c=3
10 20 30Modifying during iteration
<?php
// 引用遍历:直接改原数组
$nums = [1, 2, 3];
foreach ($nums as &$n) {
$n *= 2;
}
unset($n); // 必须解除引用!
print_r($nums);Array
(
[0] => 2
[1] => 4
[2] => 6
)unset($n) is mandatory.** After the loop $n still references the last element; reusing $n silently overwrites the array tail — an extremely subtle bug.<?php
$nums = [1, 2, 3];
foreach ($nums as &$n) {
$n *= 2;
}
// 忘了 unset
$n = 999; // 本意是给普通变量赋值
print_r($nums);Array
(
[0] => 2
[1] => 4
[2] => 999
)The last element became 999. This is nasty to debug, so many teams ban by-reference iteration in favour of array_map.
<?php
$nums = [1, 2, 3];
$doubled = array_map(fn($n) => $n * 2, $nums);
print_r($doubled);
print_r($nums);Array
(
[0] => 2
[1] => 4
[2] => 6
)
Array
(
[0] => 1
[1] => 2
[2] => 3
)for or foreach
| Situation | Use |
|---|---|
| 遍历整个数组 | foreach |
| 需要下标做运算(如取相邻元素) | for + count |
| 只需循环 N 次,与数组无关 | for |
| 数组可能被 unset 留下空洞 | foreach |
| 需要倒序遍历 | for 从 count-1 开始,或 array_reverse |
for ($i = 0; $i < count($arr); $i++) calls count() each iteration. Hoist it: for ($i = 0, $len = count($arr); $i < $len; $i++).
Generators: for large data
Iterating hundreds of thousands of rows loads the whole array. A yield generator produces values on demand with constant memory.
<?php
function lines(int $n): Generator {
for ($i = 1; $i <= $n; $i++) {
yield $i => $i * $i;
}
}
$sum = 0;
foreach (lines(5) as $i => $sq) {
echo "$i^2=$sq\n";
$sum += $sq;
}
echo "合计 $sum\n";1^2=1
2^2=4
3^2=9
4^2=16
5^2=25
合计 55for 的三段式
<?php
for ($i = 1; $i <= 3; $i++) {
echo "第 $i 次\n";
}
// 递减
for ($i = 3; $i >= 1; $i--) {
echo $i . ($i > 1 ? "," : "");
}
echo "\n";
// 多表达式
for ($i = 0, $j = 10; $i < $j; $i += 3, $j -= 3) {
echo "i=$i j=$j\n";
}第 1 次
第 2 次
第 3 次
3,2,1
i=0 j=10
i=3 j=7foreach 遍历数组
foreach 只遍历数组和对象,不依赖索引,比 for 安全得多。
<?php
$arr = ["a" => 1, "b" => 2, "c" => 3];
$pairs = [];
foreach ($arr as $k => $v) {
$pairs[] = "$k=$v";
}
echo implode(", ", $pairs), "\n";
// 只取值
foreach ([10, 20, 30] as $v) {
echo $v . " ";
}
echo "\n";a=1, b=2, c=3
10 20 30遍历时修改数组
<?php
// 引用遍历:直接改原数组
$nums = [1, 2, 3];
foreach ($nums as &$n) {
$n *= 2;
}
unset($n); // 必须解除引用!
print_r($nums);Array
(
[0] => 2
[1] => 4
[2] => 6
)unset($n) 不能省。** 引用变量 $n 在循环结束后仍指向最后一个元素,下次再用 $n 赋值会悄悄改掉数组末尾——这个 bug 极其隐蔽。<?php
$nums = [1, 2, 3];
foreach ($nums as &$n) {
$n *= 2;
}
// 忘了 unset
$n = 999; // 本意是给普通变量赋值
print_r($nums);Array
(
[0] => 2
[1] => 4
[2] => 999
)最后一个元素被改成了 999。这类问题排查起来要人命,所以团队规范里通常直接禁止引用遍历,改用 array_map。
<?php
$nums = [1, 2, 3];
$doubled = array_map(fn($n) => $n * 2, $nums);
print_r($doubled);
print_r($nums);Array
(
[0] => 2
[1] => 4
[2] => 6
)
Array
(
[0] => 1
[1] => 2
[2] => 3
)for 还是 foreach
| 场景 | 选择 |
|---|---|
| 遍历整个数组 | foreach |
| 需要下标做运算(如取相邻元素) | for + count |
| 只需循环 N 次,与数组无关 | for |
| 数组可能被 unset 留下空洞 | foreach |
| 需要倒序遍历 | for 从 count-1 开始,或 array_reverse |
for ($i = 0; $i < count($arr); $i++) 每次都调 count(),虽然 PHP 内部有缓存,但写成 for ($i = 0, $len = count($arr); $i < $len; $i++) 更明确。
生成器:大数组的替代方案
遍历几十万行数据时,foreach 一个完整数组会吃掉大量内存。用 yield 的生成器按需产出,内存恒定。
<?php
function lines(int $n): Generator {
for ($i = 1; $i <= $n; $i++) {
yield $i => $i * $i;
}
}
$sum = 0;
foreach (lines(5) as $i => $sq) {
echo "$i^2=$sq\n";
$sum += $sq;
}
echo "合计 $sum\n";1^2=1
2^2=4
3^2=9
4^2=16
5^2=25
合计 55