第2章 2.2 while/for/foreach 循环:让电脑替你做重复的事
🎯 开场:为什么要学循环?
上一章我们学了 if/switch 条件分支,就像给程序装上了"红绿灯"——遇到不同情况走不同的路。
但你有没有遇到过这种情况:
- 老师让你把全班 50 个同学的成绩一个个打印出来
- 双十一要统计自己一年来每笔购物开销
- 写爬虫要把 1000 条数据全部存进数据库
这时你怎么办?写 50 次 print()?写 1000 次?那不得累死?
循环就是来解决这个问题的——让电脑替你做重复的事,你只需要告诉它"怎么做"和"做多少次"。
学完这一章,你将能够:
- 用 3 行代码搞定原本需要写 100 行的活儿
- 自动化处理 Excel、CSV 文件里的数据
- 写出一个能自动抓取网页内容的小爬虫
🧱 基础:三种循环器,适用不同场景
为什么需要三种循环?
打个比方:你去餐厅吃饭
- for 循环:你知道要吃几碗饭(固定次数)
- while 循环:你不知道吃几\n\n
\n\n
\n\n碗,一直吃到吃饱为止(条件满足就继续) - foreach 循环:服务员端上一桌菜,你挨个尝一遍(遍历每个元素)
while 循环:直到满足条件为止
生活类比:你妈说"考到 90 分就给你买手机",你一直考,直到考到 90 分为止。
<?php
$分数 = 0;
while ($分数 < 90) {
echo "这次考了 $分数 分,继续努力!\n";
$分数 = $分数 + 15; // 每次提高 15 分
}
echo "终于考到 $分数 分,可以买手机了!";
?>
这次考了 0 分,继续努力!
这次考了 15 分,继续努力!
这次考了 30 分,继续努力!
这次考了 45 分,继续努力!
这次考了 60 分,继续努力!
这次考了 75 分,继续努力!
终于考到 90 分,可以买手机了!
说白了:while 就是"只要还没达标,就一直干"。
⚠️ 注意:一定要有让条件变假的机制(比如
$分数 += 15),不然会变成死循环,程序卡死。
for 循环:知道要循环几次
生活类比:体测跑步,要跑 400 米,就是跑 4 圈。每圈就是一次循环。
<?php
// 打印 1 到 5
for ($i = 1; $i <= 5; $i++) {
echo "第 $i 圈\n";
}
echo "跑完了!";
?>
第 1 圈
第 2 圈
第 3 圈
第 4 圈
第 5 圈
跑完了!
拆解一下:
- $i = 1:从哪开始(计数器初始化)
- $i <= 5:什么时候停(循环条件)
- $i++:每次怎么变(计数器更新)
💡 小技巧:
$i++等于$i = $i + 1,这是程序员最常用的简写。
foreach 循环:挨个处理数组里的东西
生活类比:你妈让你"把冰箱里每个苹果都咬一口"。你不需要数冰箱里有几个苹果,挨个来就是了。
<?php
$水果列表 = ["苹果", "香蕉", "橙子", "葡萄"];
foreach ($水果列表 as $水果) {
echo "吃掉一个 $水果\n";
}
echo "水果全吃完了!";
?>
吃掉一个 苹果
吃掉一个 香蕉
吃掉一个 橙子
吃掉一个 葡萄
水果全吃完了!
如果需要知道当前是第几个呢?
<?php
$水果列表 = ["苹果", "香蕉", "橙子", "葡萄"];
foreach ($水果列表 as $索引 => $水果) {
echo "第 " . ($索引 + 1) . " 个是 $水果\n";
}
?>
第 1 个是 苹果
第 2 个是 香蕉
第 3 个是 橙子
第 4 个是 葡萄
break 和 continue:循环里的"快捷键"
break:直接跳出循环,不干了(相当于老板说"够了,不用做了")continue:跳过这一次,继续下一次(相当于说"这个不行,下一个")
<?php
// 场景:打印 1-10,但跳过 5,遇到 8 就停止
for ($i = 1; $i <= 10; $i++) {
if ($i == 8) {
echo "遇到 8,不打了,收工!\n";
break; // 整个循环结束
}
if ($i == 5) {
echo "跳过 5,继续!\n";
continue; // 跳过本次循环,继续下一个
}
echo "数字是 $i\n";
}
?>
数字是 1
数字是 2
数字是 3
数字是 4
跳过 5,继续!
数字是 6
数字是 7
遇到 8,不打了,收工!
🔥 实战:三个小项目
项目 1:猜数字游戏(5 分钟)
场景:电脑想一个 1-100 的数字,你来猜,电脑告诉你猜大了还是小了。
<?php
// 电脑想一个随机数字
$目标数字 = rand(1, 100);
$猜测次数 = 0;
echo "欢迎来玩猜数字游戏!1-100 之间,你猜是几?\n";
while (true) {
$猜测次数++;
echo "请输入你的猜测:";
$猜测 = trim(fgets(STDIN)); // 读取用户输入
if ($猜测 == $目标数字) {
echo "恭喜你!猜对了!答案是 $目标数字\n";
echo "你一共猜了 $猜测次数 次";
break;
} elseif ($猜测 < $目标数字) {
echo "太小了,往大猜!\n";
} else {
echo "太大了,往小猜!\n";
}
}
?>
运行示例:
欢迎来玩猜数字游戏!1-100 之间,你猜是几?
请输入你的猜测:50
太小了,往大猜!
请输入你的猜测:75
太大了,往小猜!
请输入你的猜测:62
太小了,往大猜!
请输入你的猜测:68
恭喜你!猜对了!答案是 68
你一共猜了 4 次
说白了:用 while(true) 配合 break,实现"一直猜,直到猜对为止"。
项目 2:批量处理 CSV 文件(15 分钟)
场景:有一个 CSV 文件 scores.csv,里面是学生成绩,要统计平均分、最高分、最低分。
CSV 文件内容(scores.csv):
姓名,语文,数学,英语
张三,85,92,88
李四,90,78,95
王五,76,85,90
赵六,88,91,82
<?php
// 读取 CSV 文件
$文件名 = "scores.csv";
$行数 = 0;
$总行 = count(file($文件名)) - 1; // 减去表头
$语文总分 = 0;
$数学总分 = 0;
$英语总分 = 0;
$最高总分行 = [];
echo "=== 成绩统计 ===\n";
// 打开文件,读取每一行
$handle = fopen($文件名, "r");
while (($数据 = fgetcsv($handle)) !== false) {
// 第一行是表头,跳过
if ($行数 == 0) {
$行数++;
continue;
}
// 累加各科成绩
$语文总分 += $数据[1];
$数学总分 += $数据[2];
$英语总分 += $数据[3];
// 计算个人总分,存入数组
$个人总分 = $数据[1] + $数据[2] + $数据[3];
$最高总分行[] = [
'姓名' => $数据[0],
'总分' => $个人总分
];
$行数++;
}
fclose($handle);
// 按总分排序,取最高分
array_multisort(array_column($最高总分行, '总分'), SORT_DESC, $最高总分行);
echo "总人数:$总行 人\n";
echo "语文平均分:" . round($语文总分 / $总行, 1) . " 分\n";
echo "数学平均分:" . round($数学总分 / $总行, 1) . " 分\n";
echo "英语平均分:" . round($英语总分 / $总行, 1) . " 分\n";
echo "\n最高分同学:" . $最高总分行[0]['姓名'] . ",总分 " . $最高总分行[0]['总分'] . " 分\n";
?>
输出:
=== 成绩统计 ===
总人数:4 人
语文平均分:84.8 分
数学平均分:86.5 分
英语平均分:88.8 分
最高分同学:李四,总分 263 分
说白了:用 while + fgetcsv 逐行读取文件,累加计算,最后用 array_multisort 排序。
项目 3:待办清单小工具(15 分钟)
场景:做一个命令行待办清单,可以添加任务、查看任务、删除完成的任务。数据存成 JSON 文件,关闭程序后再打开还在。
<?php
$文件名 = "todo.json";
// 初始化:如果文件不存在,创建一个空列表
if (!file_exists($文件名)) {
file_put_contents($文件名, json_encode([]));
}
// 加载已有任务
$任务列表 = json_decode(file_get_contents($文件名), true);
echo "=== 待办清单工具 ===\n";
echo "命令:add 查看(查看) 删除 删除(deld) 退出(quit)\n\n";
while (true) {
echo "请输入命令:";
$命令 = trim(fgets(STDIN));
if ($命令 === "quit") {
echo "再见!\n";
break;
}
if ($命令 === "add") {
echo "输入任务内容:";
$内容 = trim(fgets(STDIN));
// 添加新任务,包含唯一 ID、时间和完成状态
$任务列表[] = [
'id' => time(), // 用时间戳当唯一 ID
'内容' => $内容,
'完成' => false,
'时间' => date("Y-m-d H:i")
];
echo "✅ 任务已添加:$内容\n";
}
elseif ($命令 === "查看" || $命令 === "查看") {
if (empty($任务列表)) {
echo "📝 暂无任务\n";
} else {
echo "=== 待办清单 ===\n";
foreach ($任务列表 as $索引 => $任务) {
$状态 = $任务['完成'] ? "✅" : "⬜";
$前缀 = $任务['完成'] ? " " : "[$索引] ";
echo "$前缀 $状态 " . $任务['内容'] . " (" . $任务['时间'] . ")\n";
}
echo "共 " . count($任务列表) . " 个任务\n";
}
}
elseif ($命令 === "del" || $命令 === "删除") {
echo "输入要删除的任务编号:";
$编号 = trim(fgets(STDIN));
if (isset($任务列表[$编号])) {
$删除的任务 = $任务列表[$编号]['内容'];
unset($任务列表[$编号]);
$任务列表 = array_values($任务列表); // 重新索引
echo "🗑️ 已删除:$删除的任务\n";
} else {
echo "❌ 编号不存在\n";
}
}
else {
echo "❓ 未知命令\n";
}
// 每次操作后保存到文件
file_put_contents($文件名, json_encode($任务列表, JSON_UNESCAPED_UNICODE));
}
?>
运行示例:
=== 待办清单工具 ===
命令:add 查看 查看 删除 删除(Del) 退出(quit)
请输入命令:add
输入任务内容:买菜
✅ 任务已添加:买菜
请输入命令:add
输入任务内容:做饭
✅ 任务已添加:做饭
请输入命令:查看
=== 待办清单 ===
[0] ⬜ 买菜 (2024-01-15 14:30)
[1] ⬜ 做饭 (2024-01-15 14:31)
共 2 个任务
请输入命令:del
输入要删除的任务编号:0
🗑️ 已删除:买菜
请输入命令:quit
再见!
说白了:while(true) 让程序一直运行,用 json_decode/encode 实现数据持久化。
💪 进阶:常见坑 + 技巧
坑 1:死循环
<?php
// ❌ 错误示例:忘记更新计数器
$数字 = 1;
while ($数字 <= 10) {
echo "$数字\n";
// 忘记 $数字++,程序卡死
}
// ✅ 正确写法
$数字 = 1;
while ($数字 <= 10) {
echo "$数字\n";
$数字++; // 别忘了更新
}
?>
坑 2:foreach 修改元素值
<?php
// ❌ 错误示例:想在 foreach 里修改原数组
$numbers = [1, 2, 3];
foreach ($numbers as $n) {
$n = $n * 2; // 只是修改了副本
}
print_r($numbers); // 还是 [1, 2, 3]
// ✅ 正确写法:引用传递
$numbers = [1, 2, 3];
foreach ($numbers as &$n) { // 加 &
$n = $n * 2;
}
print_r($numbers); // [2, 4, 6]
unset($n); // 解除引用,避免后续误用
?>
坑 3:循环里的变量作用域
<?php
// ❌ 错误示例:以为循环外还能用到 $i
for ($i = 0; $i < 5; $i++) {
if ($i == 2) break;
}
echo $i; // PHP 7.1+ 会报错 "Undefined variable: i"
// ✅ 正确写法:需要就提前声明,或者用其他方式记录
$i = 0;
for ($i = 0; $i < 5; $i++) {
if ($i == 2) break;
}
echo $i; // 输出 2
?>
坑 4:fgetcsv 读取 BOM 问题
<?php
// ❌ 错误示例:CSV 文件有 BOM 头,导致第一列键名出错
$handle = fopen("scores.csv", "r");
$row = fgetcsv($handle);
print_r($row); // ["\xEF\xBB\xBF姓名", "语文", "数学", "英语"]
// ✅ 正确写法:去掉 BOM
$handle = fopen("scores.csv", "r");
$row = fgetcsv($handle);
if (substr($row[0], 0, 3) === "\xEF\xBB\xBF") {
$row[0] = substr($row[0], 3);
}
print_r($row); // ["姓名", "语文", "数学", "英语"]
?>
坑 5:while 条件写成赋值
<?php
// ❌ 危险示例:= 和 == 搞混
$数字 = 10;
while ($数字 = 5) { // 赋值总是返回 5(真),死循环!
echo "危险!\n";
}
// ✅ 正确写法
$数字 = 10;
while ($数字 == 5) { // 比较,不是赋值
echo "不会进来";
}
?>
性能小贴士:预分配数组大小
<?php
// 如果知道要存多少数据,先分配内存更快
$总数 = 10000;
$数组 = [];
// ❌ 慢:每次追加都可能导致内存重分配
for ($i = 0; $i < $总数; $i++) {
$数组[] = $i;
}
// ✅ 快:预分配内存
$数组 = array_fill(0, $总数, null);
for ($i = 0; $i < $总数; $i++) {
$数组[$i] = $i;
}
?>
调试技巧:print_r 和 var_dump
<?php
$数组 = ["苹果" => 3, "香蕉" => 5];
// print_r:好看,适合看结构
echo "print_r 输出:\n";
print_r($数组);
// var_dump:更详细,适合调试
echo "\nvar_dump 输出:\n";
var_dump($数组);
// 输出:
// print_r 输出:
// Array
// (
// [苹果] => 3
// [香蕉] => 5
// )
//
// var_dump 输出:
// array(2) {
// ["苹果"]=>
// int(3)
// ["香蕉"]=>
// int(5)
// }
?>
✏️ 练习题
练习 1(2 分钟):数兔子
- 输入:无
- 预期输出:
1 只兔子
2 只兔子
3 只兔子
4 只兔子
5 只兔子
- 提示:用 for 循环,从 1 数到 5
练习 2(3 分钟):偶数过滤器
- 输入:1 到 10 的数字
- 预期输出:2、4、6、8、10(只打印偶数)
- 提示:在循环里加一个 if 判断
$i % 2 == 0
练习 3(5 分钟):水果店记账
- 输入:一个水果数组
["苹果"=>30, "香蕉"=>20, "橙子"=>25] - 预期输出:打印每种水果的单价和总价(数量×单价=总价)
- 提示:用 foreach 遍历 key-value
练习 4(5 分钟):改改猜数字
- 输入:在项目 1 的猜数字游戏中,加一个功能:如果猜了 10 次还没猜中,就提示"太笨了,游戏结束"
- 预期输出:第 10 次猜测后输出"太笨了,游戏结束"并退出
- 提示:加一个
$猜测次数 >= 10的判断
练习 5(5 分钟):报错分析
- 输入:以下代码运行时报错 "Maximum execution time of 30 seconds exceeded"
<?php
$i = 1;
while ($i > 0) {
$i++;
}
?>
- 预期输出:说明哪里出了问题,如何修复
- 提示:
while里的条件永远不会变,导致无限循环
作业:做一个「批量重命名文件工具」
需求:做一个命令行工具,可以:
1. 列出指定目录下所有文件
2. 按规则批量重命名(如在所有文件名前加前缀、替换某些字符)
3. 支持预览(不改名,只看改后会是什么样)
功能点:
- 用 glob() 或 scandir() 遍历目录
- 用 rename() 重命名文件
- 至少支持两种重命名规则
加分项:
- 支持正则表达式匹配
- 支持撤销操作(记录操作日志)
验收标准:
- 能列出目录文件
- 能预览重命名结果
- 确认执行后真正改名
- 代码有适当注释
提交方式:评论区贴代码或 GitHub 链接
📚 总结
本文学到的 3 个核心点:
1. while 循环——不知道次数,"条件满足就一直干"
2. for 循环——知道次数,"初始化 → 条件 → 更新" 三步走
3. foreach 循环——遍历数组,"挨个处理每个元素"
延伸学习资源:
- PHP 官方文档:循环结构
- 《PHP 核心技术与最佳实践》——第 3 章有更深入的循环讲解
- 视频:B站「php中文网」循环专题
互动钩子:
你在实际开发中,用循环最多的是哪种场景?是处理用户输入、读写文件、还是调 API?评论区聊聊你是怎么用的!
下章预告:
学会了循环,你已经能让程序"重复干活"了。但重复的逻辑写多了也很麻烦——比如你要计算 10 个不同的成绩,平均分要写 10 遍怎么办?下一章我们来学习函数,让你把常用的逻辑打包成"工具包",想用的时候直接调用!

评论(0)