foreach 循环
foreach 是 PHP 中遍历数组和对象最便捷的循环结构。它自动迭代数组中的每个元素,无需手动管理索引或计数器。foreach 适用于索引数组和关联数组,是 PHP 中最常用的循环方式。
基础概念
foreach 只能用于遍历数组和对象。当 foreach 开始执行时,数组内部的指针会自动重置到第一个元素。
foreach 有两种语法形式:
- 仅取值:
foreach ($array as $value) - 取键值对:
foreach ($array as $key => $value)
不可用于标量值
foreach 不能用于遍历标量值(如整数、浮点数、字符串)。尝试遍历非可迭代值会产生 Warning(PHP 8.0+)或 Error。
语法结构
遍历值
<?php
declare(strict_types=1);
$fruits = ['苹果', '香蕉', '橙子', '葡萄'];
foreach ($fruits as $fruit) {
echo $fruit . "\n";
}
// 输出:
// 苹果
// 香蕉
// 橙子
// 葡萄遍历键值对
<?php
declare(strict_types=1);
$user = [
'name' => '张三',
'age' => 28,
'email' => 'zhang@example.com',
'city' => '北京',
];
foreach ($user as $key => $value) {
echo "{$key}: {$value}\n";
}
// 输出:
// name: 张三
// age: 28
// email: zhang@example.com
// city: 北京遍历整数索引数组
<?php
declare(strict_types=1);
$scores = [95, 87, 92, 78, 88];
foreach ($scores as $index => $score) {
echo "学生 #{$index} 成绩:{$score}\n";
}
// 输出:
// 学生 #0 成绩:95
// 学生 #1 成绩:87
// 学生 #2 成绩:92
// 学生 #3 成绩:78
// 学生 #4 成绩:88foreach 的替代语法
<?php
declare(strict_types=1);
$items = [
['name' => '笔记本电脑', 'price' => 5999],
['name' => '无线鼠标', 'price' => 89],
];
?>
<ul>
<?php foreach ($items as $item): ?>
<li>
<?php echo htmlspecialchars($item['name']); ?>
- ¥<?php echo number_format($item['price'], 2); ?>
</li>
<?php endforeach; ?>
</ul>详细说明
通过引用修改元素
使用 &$value 可以在循环体内直接修改原数组的元素。引用遍历时,$value 是数组元素的一个引用,对它的修改会直接影响原数组。
<?php
declare(strict_types=1);
$prices = [100, 200, 300, 400];
// 通过引用将每个价格增加 10%
foreach ($prices as &$price) {
$price = (int) ($price * 1.1);
}
unset($price); // 重要:取消引用,避免后续代码中的意外修改
print_r($prices);
// 输出:Array ( [0] => 110 [1] => 220 [2] => 330 [3] => 440 )必须使用 unset() 解除引用
在引用遍历结束后,$value 仍然是对数组最后一个元素的引用。如果不使用 unset($value) 解除引用,后续对 $value 的赋值可能会意外修改数组的最后一个元素。
<?php
declare(strict_types=1);
// 错误示例:忘记 unset 导致的 bug
$arr = [1, 2, 3];
foreach ($arr as &$value) {
$value *= 2;
}
// 忘记 unset($value)
$value = 99; // 这会意外修改 $arr[2] 的值!
print_r($arr);
// 输出:Array ( [0] => 2 [1] => 4 [2] => 99 )
// 正确做法
$arr = [1, 2, 3];
foreach ($arr as &$value) {
$value *= 2;
}
unset($value);
$value = 99; // 现在是安全的,不影响数组
print_r($arr);
// 输出:Array ( [0] => 2 [1] => 4 [2] => 6 )遍历嵌套数组
嵌套数组(多维数组)需要使用嵌套的 foreach 来遍历。
<?php
declare(strict_types=1);
$students = [
['name' => '张三', 'scores' => [85, 92, 78]],
['name' => '李四', 'scores' => [90, 88, 95]],
['name' => '王五', 'scores' => [76, 82, 91]],
];
foreach ($students as $student) {
echo "学生:{$student['name']}\n";
foreach ($student['scores'] as $subject => $score) {
echo " 科目 {$subject}:{$score} 分\n";
}
$average = array_sum($student['scores']) / count($student['scores']);
echo " 平均分:{$average}\n";
}遍历对象
foreach 可以遍历实现了 Traversable 接口的对象,包括 Iterator、IteratorAggregate 以及 ArrayObject 等。
<?php
declare(strict_types=1);
class ColorCollection implements \IteratorAggregate
{
private array $colors = [];
public function __construct(array $colors)
{
$this->colors = $colors;
}
public function add(string $color): void
{
$this->colors[] = $color;
}
public function getIterator(): \ArrayIterator
{
return new \ArrayIterator($this->colors);
}
}
$collection = new ColorCollection(['红色', '绿色', '蓝色']);
$collection->add('黄色');
foreach ($collection as $color) {
echo $color . "\n";
}
// 输出:
// 红色
// 绿色
// 蓝色
// 黄色在循环中修改数组
循环中修改数组的行为
在 foreach 循环中修改正在遍历的数组可能导致意外的行为。PHP 的 foreach 在遍历开始时会创建数组的内部副本(非引用遍历时),因此在循环体内添加或删除元素不会影响遍历过程。
<?php
declare(strict_types=1);
// 在非引用遍历中添加元素不会影响当前遍历
$items = ['a', 'b', 'c'];
foreach ($items as $item) {
echo $item . "\n";
if ($item === 'b') {
$items[] = 'd'; // 添加元素,但不会在当前遍历中被访问
}
}
// 输出:a, b, c(不包含 d)
// 引用遍历中修改是安全的
$items = ['a', 'b', 'c'];
foreach ($items as $key => &$item) {
echo $item . "\n";
if ($item === 'b') {
$items[$key] = 'B'; // 修改当前元素
}
}
unset($item);
print_r($items);
// 输出:Array ( [0] => a [1] => B [2] => c )实战示例
数据过滤和转换
<?php
declare(strict_types=1);
function filterAndTransform(array $products, float $minPrice): array
{
$result = [];
foreach ($products as $product) {
if ($product['price'] < $minPrice) {
continue; // 跳过低价商品
}
$result[] = [
'name' => strtoupper($product['name']),
'price' => $product['price'],
'tax' => round($product['price'] * 0.13, 2),
];
}
return $result;
}
$products = [
['name' => '手机', 'price' => 3999],
['name' => '耳机', 'price' => 199],
['name' => '数据线', 'price' => 15],
['name' => '平板', 'price' => 2599],
];
$filtered = filterAndTransform($products, 100);
foreach ($filtered as $item) {
echo "{$item['name']}:价格 {$item['price']},税 {$item['tax']}\n";
}构建映射表
<?php
declare(strict_types=1);
function buildIdNameMap(array $users): array
{
$map = [];
foreach ($users as $user) {
$map[$user['id']] = $user['name'];
}
return $map;
}
$users = [
['id' => 101, 'name' => '张三'],
['id' => 102, 'name' => '李四'],
['id' => 103, 'name' => '王五'],
];
$map = buildIdNameMap($users);
echo "ID 101 对应:" . $map[101] . "\n";
echo "ID 103 对应:" . $map[103] . "\n";分组统计
<?php
declare(strict_types=1);
function groupByCategory(array $products): array
{
$groups = [];
foreach ($products as $product) {
$category = $product['category'];
if (!isset($groups[$category])) {
$groups[$category] = [
'count' => 0,
'revenue' => 0,
];
}
$groups[$category]['count']++;
$groups[$category]['revenue'] += $product['price'] * $product['sold'];
}
return $groups;
}
$products = [
['category' => '电子', 'price' => 3999, 'sold' => 50],
['category' => '电子', 'price' => 899, 'sold' => 120],
['category' => '图书', 'price' => 59, 'sold' => 500],
['category' => '图书', 'price' => 79, 'sold' => 300],
['category' => '服装', 'price' => 199, 'sold' => 200],
];
$groups = groupByCategory($products);
foreach ($groups as $category => $data) {
echo "分类:{$category}\n";
echo " 商品数:{$data['count']}\n";
echo " 销售额:{$data['revenue']}\n";
}注意事项
不要使用
list()解构:foreach ($array as list($a, $b))可以解构嵌套数组,但在 PHP 8.1 中可以使用更灵活的数组解构语法[$a, $b]。foreach不改变数组指针:foreach会自动操作内部数组指针,循环结束后指针会指向最后一个元素。如果需要在循环后重置,使用reset()。引用遍历的性能:引用遍历比值遍历略慢,因为 PHP 需要维护引用关系。只在需要修改元素时才使用引用。
不可遍历标量值:PHP 8.0+ 对尝试遍历
null、false、int、float、string等非可迭代值会产生TypeError。
最佳实践
优先使用
foreach遍历数组:比for循环更安全、更简洁,不会因为索引管理错误而出 bug。引用遍历后必须
unset():防止后续代码中意外修改数组元素。避免在循环中增删元素:如果需要过滤数组,先收集要操作的元素,循环结束后再统一处理,或使用
array_filter()、array_map()等函数。考虑使用函数式方法:对于简单的过滤和转换,
array_filter()、array_map()、array_reduce()等函数可能更清晰。使用
readonly防止意外修改(PHP 8.1+):在需要只读遍历时,考虑使用只读属性或不变量来防止意外修改。
下一节
循环语句的学习到此结束。接下来了解跳转与终止语句,首先是 break 和 continue。