タイトル: 繰り返し処理
SEOタイトル: PHP for / foreach / while の使い分けと無限ループ・多重ループ脱出
| この記事の要点 |
|
for 文の基本構文
<?php
// for (初期化; 継続条件; 更新)
for ($i = 0; $i < 10; $i++) {
echo $i . "\n"; // 0, 1, ..., 9
}
// 逆順
for ($i = 9; $i >= 0; $i--) {
echo $i;
}
// 2 ずつ
for ($i = 0; $i < 20; $i += 2) {
echo $i . " "; // 0 2 4 ... 18
}
// 浮動小数も可能 (誤差注意)
for ($x = 0.0; $x < 1.0; $x += 0.1) {
echo $x . "\n";
}
// 複数式 (カンマ区切り)
for ($i = 0, $j = 10; $i < $j; $i++, $j--) {
// 中央で交差したら終了
}
count() をループ条件で呼ぶ罠
<?php
$arr = range(1, 1000);
// ❌ 毎回 count() が呼ばれて 1000 回呼ばれる (PHP 7+ は最適化されるが意図不明瞭)
for ($i = 0; $i < count($arr); $i++) {
// ...
}
// ✅ 事前に変数化
$n = count($arr);
for ($i = 0; $i < $n; $i++) {
// ...
}
// ✅ 配列なら foreach の方が早く意図も明確
foreach ($arr as $v) {
// ...
}
foreach: 配列の標準
<?php
$fruits = ['apple', 'banana', 'cherry'];
// 値のみ
foreach ($fruits as $fruit) {
echo $fruit . "\n";
}
// キーと値
foreach ($fruits as $i => $fruit) {
echo "$i: $fruit\n";
}
// 連想配列
$user = ['name' => 'Alice', 'age' => 30, 'email' => 'a@example.com'];
foreach ($user as $key => $value) {
echo "$key => $value\n";
}
// 参照渡しで要素を直接更新
$nums = [1, 2, 3];
foreach ($nums as &$n) {
$n *= 2;
}
unset($n); // ★ 必ず参照を解除 (重要)
print_r($nums); // [2, 4, 6]
// ネストした foreach
$matrix = [[1,2,3], [4,5,6], [7,8,9]];
foreach ($matrix as $row) {
foreach ($row as $cell) {
echo $cell . " ";
}
echo "\n";
}
for vs foreach の使い分け
| 条件 | 推奨 | 理由 |
|---|---|---|
| 配列の全要素を順番に処理 | foreach | 意図が明確、高速 |
| キー(インデックス)を演算に使う | for | i+1 番目との比較などに便利 |
| 配列の途中まで / 途中から | for or array_slice + foreach | 柔軟性 |
| 数値範囲を扱う (回数指定) | for | 明示的 |
| 反復可能オブジェクト (Iterator) | foreach | 標準対応 |
| 無限ループ・条件次第で終了 | while | 条件が複雑な時に |
while / do-while
<?php
// 条件が真の間繰り返す
$n = 1;
while ($n < 100) {
$n *= 2; // 1, 2, 4, 8, 16, 32, 64, 128で抜ける
}
// do-while: 最低 1 回は実行
$pass = 'guest';
do {
$pass = readline('Password: ');
} while ($pass !== 'secret');
// 行ごとに読み込む典型パターン
$fp = fopen('data.txt', 'r');
while (($line = fgets($fp)) !== false) {
echo $line;
}
fclose($fp);
break / continue で制御
<?php
// break: ループを抜ける
foreach ($users as $u) {
if ($u->isAdmin()) {
$admin = $u;
break; // 最初の管理者で終了
}
}
// continue: 次のイテレーションへ
foreach ($scores as $s) {
if ($s < 0) continue; // 負の値はスキップ
$total += $s;
}
// break/continue にレベル指定 (PHP 独自)
foreach ($matrix as $row) {
foreach ($row as $cell) {
if ($cell === 'X') {
break 2; // ★ 2 重ループを一気に脱出
}
}
}
// 内側の continue 2 で外側の次イテレーションへ
foreach ($groups as $group) {
foreach ($group->items as $item) {
if ($item->skip) continue 2; // 外側のループの次の group へ
// ...
}
}
無限ループの書き方と回避
<?php
// 意図的な無限ループ
while (true) {
$line = fgets(STDIN);
if ($line === false || trim($line) === 'quit') break;
echo "echo: $line";
}
// for で同じこと
for (;;) {
// ...
if ($done) break;
}
// ❌ 意図しない無限ループ: 更新忘れ
$i = 0;
while ($i < 10) {
echo $i;
// $i++; を忘れた → 永遠に動く
}
// ❌ 浮動小数誤差で抜けられない
for ($x = 0.0; $x !== 1.0; $x += 0.1) {
// 0.99999... のため永遠に等しくならない
}
// ✅ <= で
for ($x = 0.0; $x < 1.0 + 1e-9; $x += 0.1) { ... }
// 暴走対策のセーフティ
$maxIter = 10000;
$i = 0;
while ($conditionsNotMet) {
if (++$i > $maxIter) throw new RuntimeException('iteration limit');
// ...
}
foreach の参照渡し: ありがちなバグ
<?php
$arr = [1, 2, 3];
// 1 回目: 参照で全要素を 2 倍
foreach ($arr as &$v) {
$v *= 2;
}
// この時点で $arr = [2, 4, 6]、$v はまだ $arr[2] への参照
// ❌ unset($v) を忘れて 2 回目の foreach (値渡し)
foreach ($arr as $v) {
// ループ内で $v に代入 → $arr[2] が書き換えられる!
}
// $arr が壊れる: [2, 4, 4] になる
// ✅ 必ず unset
foreach ($arr as &$v) { $v *= 2; }
unset($v); // 必須
性能比較 (参考)
<?php
// 100 万要素配列の合計
$arr = range(1, 1_000_000);
// 1. foreach 値のみ (最速)
$t = microtime(true); $sum = 0;
foreach ($arr as $v) $sum += $v;
echo "foreach: " . (microtime(true) - $t) . "s\n";
// 2. for + count() を変数化
$t = microtime(true); $sum = 0; $n = count($arr);
for ($i = 0; $i < $n; $i++) $sum += $arr[$i];
echo "for: " . (microtime(true) - $t) . "s\n";
// 3. 組み込み関数 array_sum (圧倒的に速い)
$t = microtime(true);
$sum = array_sum($arr);
echo "array_sum: " . (microtime(true) - $t) . "s\n";
// 一般に: array_sum > foreach > for
// 組み込み関数があるなら最優先
FAQ
Q: foreach 中に配列要素を削除したい
A: unset($arr[$key]) は foreach 中でも安全(コピー走査)。ただし挙動が直感的でないケースもあるため、新配列に欲しい要素を集める方法を推奨。
Q: 配列が大きくて foreach が遅い
A: ジェネレータ (yield) でメモリ節約。あるいは組み込み関数 (array_map / array_filter) を活用。
Q: 連想配列の特定キーから走査開始したい
A: array_search でキー位置を得て array_slice、あるいは foreach + flag 変数で開始制御。