foreach 是 PHP 專為陣列遍歷設計的語言結構,語法簡潔、功能強大,支援索引陣列、關聯陣列、物件屬性遍歷。掌握其運作機制、引用行為與常見陷阱,能寫出更安全、高效的程式碼。
<?php
$arr = ['a' => '網上', 'b' => '朋友', 'c' => '其他'];
// 形式 1:只取值
foreach ($arr as $value) {
echo $value . "\n";
}
// 形式 2:取鍵值對(關聯陣列常用)
foreach ($arr as $key => $value) {
echo "$key => $value\n";
}
?>
foreach 在開始時建立陣列的**內部指標副本**,不影響原陣列指標$value 是元素值的**副本**,修改不影響原陣列$key 無法修改<?php
$numbers = [1, 2, 3, 4];
foreach ($numbers as &$value) {
$value *= 2; // 修改原陣列
}
print_r($numbers);
// 輸出: [2, 4, 6, 8]
?>
<?php
$arr = ['a', 'b', 'c'];
// 第一個迴圈:使用引用
foreach ($arr as &$row) {
$row = strtoupper($row);
}
// 此時 $row 仍參考 $arr[2] ('C')
// 第二個迴圈:不使用引用
foreach ($arr as $row) {
echo $row . ' ';
}
// 輸出: A B B ← 最後一個變成 B 而非 C!
?>
foreach 結束後,$row 仍**參考最後一個元素 ($arr[2])foreach 以**按值複製**方式遍歷,但每次迴圈將值**賦值給 $row**$row 是引用,賦值操作實際**修改了 $arr[2] 的值**$arr[2] 覆寫為倒數第二個元素的值 ('B')<?php
foreach ($arr as &$row) {
$row = strtoupper($row);
}
unset($row); // 關鍵:斷開引用連結
foreach ($arr as $row) {
echo $row . ' '; // 正確輸出: A B C
}
?>
<?php
foreach ($arr as $key => $row) {
$arr[$key] = strtoupper($row); // 直接修改原陣列
}
// 無引用殘留問題
?>
<?php
// 適用於重新索引的數值陣列
$arr = array_values(array_map('strtoupper', $arr));
?>
<?php
$users = [
['name' => 'Alice', 'roles' => ['admin', 'editor']],
['name' => 'Bob', 'roles' => ['user']],
];
foreach ($users as $user) {
echo "User: {$user['name']}\n";
foreach ($user['roles'] as $role) {
echo " - Role: $role\n";
}
}
?>
<?php
// PHP 7.1+ 支援陣列解構
$people = [
['name' => 'Alice', 'age' => 30],
['name' => 'Bob', 'age' => 25],
];
foreach ($people as ['name' => $name, 'age' => $age]) {
echo "$name is $age years old\n";
}
?>
<?php
class UserCollection implements IteratorAggregate {
private $users = [];
public function add(User $user) { $this->users[] = $user; }
public function getIterator() {
return new ArrayIterator($this->users);
}
}
$collection = new UserCollection();
$collection->add(new User('Alice'));
$collection->add(new User('Bob'));
foreach ($collection as $user) {
echo $user->name . "\n";
}
?>
<?php
function fibonacci($limit) {
$a = 0; $b = 1;
for ($i = 0; $i < $limit; $i++) {
yield $a;
[$a, $b] = [$b, $a + $b];
}
}
foreach (fibonacci(10) as $num) {
echo $num . ' '; // 0 1 1 2 3 5 8 13 21 34
}
?>
<?php
// ❌ 差:每次迴圈呼叫 count()
for ($i = 0; $i < count($arr); $i++) { ... }
// ✅ 好:預先計算
$count = count($arr);
for ($i = 0; $i < $count; $i++) { ... }
// foreach 本身無此問題,但避免在迴圈中做昂貴操作
foreach ($items as $item) {
// ❌ 差:重複資料庫查詢
$data = queryDatabase($item->id);
// ✅ 好:批次查詢或快取
}
?>
<?php
// 大陣列複製成本高
foreach ($hugeArray as $item) { ... } // 內部複製指標,非複製整個陣列,相對高效
// 但若需修改,引用模式避免複製值
foreach ($hugeArray as &$item) { $item['processed'] = true; }
unset($item);
?>
| 場景 | 推薦方式 |
|---|---|
| 單純遍歷讀取 | foreach ($arr as $v) |
| 需鍵名 | foreach ($arr as $k => $v) |
| 需修改原陣列 | foreach ($arr as &$v) + unset($v) |
| 需索引控制/跳過 | for ($i=0; $i<count; $i++) |
| 條件過濾 | array_filter() + foreach |
| 資料轉換 | array_map() / array_column() |
| 錯誤代碼 | 問題 | 後果 | 修正 |
|---|---|---|---|
foreach ($arr as &$v) {} foreach ($arr as $v) {} | 引用殘留 | 最後一個元素被覆寫 | 中間 unset($v) |
foreach ($arr as $k => &$v) { unset($arr[$k]); } | 迴圈中刪除元素 | 行為未定義/跳過元素 | 先收集要刪除的鍵,迴圈後處理 |
foreach (getArray() as $v) | 函式回傳陣列 | 每次迴圈可能重新執行函式 (PHP < 7) | 先指派變數 $arr = getArray() |
修改 $key | $key = 'new'; | 無效果/錯誤 | 鍵名唯讀,改用 $arr[$newKey] = $v; unset($arr[$key]) |
foreach 是 PHP 陣列遍歷的首選,語意清晰、效能良好&$v) 必須配對 unset($v),否則極易產生難以除錯的邏輯錯誤array_map/array_filter/array_column 等函數式風格,程式碼更具宣告性養成「用完引用即 unset」的習慣,能省去無數除錯時光。