Day 3 - 控制流
建议用时:180-210 分钟
你将学会什么
- 程序为什么需要条件判断和循环
if / else if / else的执行顺序- 比较运算符和逻辑运算符怎么读
switch适合处理什么问题for、while、foreach分别适合什么场景break、continue、return的区别- 如何写一个带输入校验的成绩判断程序
- 如何写一个可以反复选择的菜单程序
本页只讲 C# 控制流。今天的目标是让程序不再只会从上到下一直执行,而是能根据输入选择路线,也能重复处理一组动作。
本页固定顺序
- 先学第一部分:弄懂今天最小、最重要的知识,并运行短例子。
- 再学第二部分:把刚学的知识组合成一个完整例子。
- 然后做第三部分:自己跟着敲,再完成重复训练和每日小测。
- 最后做第四部分:先独立完成作业,再用完整答案检查。
学习衔接
上一页学习的是“变量与类型”,今天继续学习“控制流”。先使用上一页已经会的写法,再只增加今天这个新知识点;如果前置内容还不能独立敲出,先回上一页复习,不要硬跳。
今天的最低通过线
第一次学习不要求背完整页。完成下面 3 项,就可以继续:
- 能用自己的话说明“控制流”解决什么问题。
- 把第一部分的短例子亲手敲完,并确认每个例子都能运行。
- 不看完整答案完成第三部分至少前 3 个例子,再主动改一个值观察结果。
第一部分:先学原理和最小知识
这一部分先解决“这是什么、为什么要这样写”。每看到一个短例子就亲手敲一遍并运行,不要先背完整程序。
今天先抓住什么
控制流解决两类问题:
选择:满足条件就走这条路,否则走另一条路
重复:同一类动作要做多次没有控制流,程序只能一行一行执行,无法处理错误输入、等级判断、菜单选择、批量数据。
名词先讲清楚
| 名词 | 直接解释 | 例子 |
|---|---|---|
| 条件 | 最终结果是 true 或 false 的判断 | score >= 60 |
| 分支 | 不同条件执行不同代码块 | if / else |
| 循环 | 重复执行一段代码 | for、while、foreach |
| 代码块 | { } 包起来的一组代码 | if 后面的 { } |
| 比较运算符 | 比较两个值 | >、>=、<、<=、==、!= |
| 逻辑运算符 | 合并多个条件 | &&、||、! |
break | 结束整个循环 | 菜单选择退出 |
continue | 跳过本轮,进入下一轮 | 跳过错误数据 |
return | 结束当前程序或方法 | 输入错误后提前结束 |
程序默认只会从上往下执行
看这段代码:
Console.WriteLine("第一行");
Console.WriteLine("第二行");
Console.WriteLine("第三行");输出一定是:
第一行
第二行
第三行没有控制流时,程序不会自己选择,也不会自己重复。
if 只看真假
if 后面的条件最终必须能得到一个 bool,也就是 true 或 false。
int age = 20;
bool canEnter = age >= 18;
if (canEnter)
{
Console.WriteLine("可以进入");
}
else
{
Console.WriteLine("不能进入");
}age >= 18 会算出 true,所以进入第一个代码块。
比较运算符怎么读
| 写法 | 读法 | 例子结果 |
|---|---|---|
a > b | a 大于 b | 10 > 3 是 true |
a >= b | a 大于等于 b | 10 >= 10 是 true |
a < b | a 小于 b | 3 < 10 是 true |
a <= b | a 小于等于 b | 3 <= 2 是 false |
a == b | a 等于 b | 3 == 3 是 true |
a != b | a 不等于 b | 3 != 4 是 true |
最容易混的是 = 和 ==:
int count = 3; // = 是赋值
Console.WriteLine(count == 3); // == 是比较逻辑运算符怎么读
| 写法 | 读法 | 什么时候为 true |
|---|---|---|
a && b | a 并且 b | 两边都成立 |
a || b | a 或者 b | 至少一边成立 |
!a | 不是 a | a 为 false 时 |
例子:
int score = 75;
bool isValid = score >= 0 && score <= 100;
bool isExcellent = score >= 90;
bool isInvalid = !isValid;
Console.WriteLine(isValid);
Console.WriteLine(isExcellent);
Console.WriteLine(isInvalid);输出:
True
False
Falseelse if 顺序为什么重要
程序会从上往下检查条件,遇到第一个成立的分支,就不会再检查后面的 else if。
错误顺序:
int score = 95;
if (score >= 60)
{
Console.WriteLine("通过");
}
else if (score >= 90)
{
Console.WriteLine("优秀");
}这段会输出:
通过因为 score >= 60 已经成立了。更严格的条件要放前面。
正确顺序:
int score = 95;
if (score >= 90)
{
Console.WriteLine("优秀");
}
else if (score >= 60)
{
Console.WriteLine("通过");
}switch 适合固定选项
如果判断的是几个固定选项,比如菜单选择 1、2、0,switch 比一串 else if 更清楚。
Console.WriteLine("1. 添加");
Console.WriteLine("2. 查询");
Console.WriteLine("0. 退出");
Console.Write("请选择: ");
string? choice = Console.ReadLine();
switch (choice)
{
case "1":
Console.WriteLine("执行添加");
break;
case "2":
Console.WriteLine("执行查询");
break;
case "0":
Console.WriteLine("退出程序");
break;
default:
Console.WriteLine("未知选项");
break;
}范围判断用 if / else if,固定选项用 switch。
四种循环怎么选
| 循环 | 适合场景 | 例子 |
|---|---|---|
for | 次数明确,常和数字下标有关 | 打印 1 到 10 |
while | 次数不确定,满足条件就继续 | 菜单反复显示 |
do while | 至少执行一次 | 先显示菜单再判断是否继续 |
foreach | 遍历集合里的每一项 | 输出所有成绩 |
控制流常用写法速查
控制流不是只会写 if 和 for,更重要的是知道常见问题应该套哪种写法。
| 需求 | 常用写法 | 例子 |
|---|---|---|
| 输入格式不对就停止 | if (!TryParse(...)) return; | 成绩必须是整数 |
| 数字范围校验 | if (score < 0 || score > 100) | 成绩必须 0 到 100 |
| 多个条件都满足 | if (nameOk && priceOk) | 名称和价格都合法 |
| 任意一个条件满足 | if (isAdmin || isOwner) | 管理员或本人可操作 |
| 固定选项菜单 | switch (choice) | 1 新增,0 退出 |
| 次数明确重复 | for | 打印 1 到 10 |
| 次数不确定重复 | while | 菜单反复显示 |
| 遍历集合 | foreach | 处理每个成绩 |
| 跳过本轮 | continue | 跳过错误数据 |
| 结束循环 | break | 菜单选择退出 |
| 累计数量 | count++ | 统计通过人数 |
| 累计总和 | sum += value | 统计总分、总价 |
模板 1:输入校验后提前结束
Console.Write("数量: ");
string? input = Console.ReadLine();
if (!int.TryParse(input, out int count))
{
Console.WriteLine("数量必须是整数");
return;
}
if (count <= 0)
{
Console.WriteLine("数量必须大于 0");
return;
}
Console.WriteLine($"数量有效: {count}");这个模板以后会反复出现:先挡住错误输入,再进入正常逻辑。
模板 2:遍历数据并统计
var scores = new List<int> { 95, 58, 76, 40, 88 };
int passedCount = 0;
int totalScore = 0;
foreach (int score in scores)
{
totalScore += score;
if (score >= 60)
{
passedCount++;
}
}
Console.WriteLine($"总分: {totalScore}");
Console.WriteLine($"通过人数: {passedCount}");+= 表示在原来的基础上继续加。passedCount++ 表示数量加 1。
模板 3:菜单循环
while (true)
{
Console.WriteLine("1. 新增");
Console.WriteLine("2. 查询");
Console.WriteLine("0. 退出");
Console.Write("请选择: ");
string? choice = Console.ReadLine();
if (choice == "0")
{
break;
}
Console.WriteLine($"你选择了: {choice}");
}菜单循环一定要有退出条件,否则程序会一直重复。
for 循环拆开看
for (int i = 1; i <= 3; i++)
{
Console.WriteLine($"第 {i} 次");
}这行可以拆成三部分:
| 部分 | 意思 |
|---|---|
int i = 1 | 从 1 开始 |
i <= 3 | 只要 i 小于等于 3,就继续 |
i++ | 每轮结束后 i 加 1 |
输出:
第 1 次
第 2 次
第 3 次while 循环必须有出口
while (true) 表示一直循环,所以里面必须有 break。
while (true)
{
Console.Write("输入 exit 退出: ");
string? input = Console.ReadLine();
if (input == "exit")
{
break;
}
Console.WriteLine($"你输入了: {input}");
}没有 break,这个循环就不会自己结束。
break、continue、return 的区别
| 写法 | 作用范围 | 效果 |
|---|---|---|
break | 循环或 switch | 结束整个循环或当前 switch |
continue | 循环 | 跳过本轮,进入下一轮 |
return | 当前方法或程序 | 直接结束当前流程 |
看例子:
for (int i = 1; i <= 5; i++)
{
if (i == 2)
{
continue;
}
if (i == 4)
{
break;
}
Console.WriteLine(i);
}输出:
1
32 被 continue 跳过,循环到 4 时被 break 结束。
今天写代码时按这个顺序
- 先写正常输入会走的路线。
- 再写错误输入会走的路线。
- 范围判断先写最严格的条件。
- 固定菜单选项优先考虑
switch。 - 需要重复时再加循环。
- 循环必须有结束条件。
- 最后用正常输入和错误输入各跑一次。
第二部分:把知识组合成完整例子
前面已经学过最小知识。现在把它们组合起来,先读懂执行顺序,再完整敲一遍。今天最终要写的程序会读取成绩,判断是否合法,再输出等级。
最终你要写出这个程序
Console.Write("请输入成绩: ");
string? input = Console.ReadLine();
if (!int.TryParse(input, out int score))
{
Console.WriteLine("成绩必须是整数");
return;
}
if (score < 0 || score > 100)
{
Console.WriteLine("成绩必须在 0 到 100 之间");
return;
}
string level;
if (score >= 90)
{
level = "优秀";
}
else if (score >= 80)
{
level = "良好";
}
else if (score >= 60)
{
level = "通过";
}
else
{
level = "未通过";
}
Console.WriteLine($"成绩: {score}");
Console.WriteLine($"等级: {level}");输入 95:
请输入成绩: 95
成绩: 95
等级: 优秀输入 abc:
请输入成绩: abc
成绩必须是整数输入 120:
请输入成绩: 120
成绩必须在 0 到 100 之间这个例子里,程序不再是简单向下执行,而是根据输入走不同路线。
第三部分:跟着敲代码
从这里开始动手。每个例子都可以直接放进 Program.cs 运行。
动手前先做这 3 件事
- 打开控制台项目,确认
dotnet run能运行。 - 每做一个例子,就把
Program.cs旧内容替换掉。 - 每次只改一个输入或一个条件,看输出怎么变。
例子 1:最简单的条件判断
int score = 78;
if (score >= 60)
{
Console.WriteLine("通过");
}
else
{
Console.WriteLine("未通过");
}预期输出:
通过把 score 改成 45,再运行一次,输出会变成“未通过”。
例子 2:成绩等级判断
int score = 95;
if (score >= 90)
{
Console.WriteLine("A");
}
else if (score >= 80)
{
Console.WriteLine("B");
}
else if (score >= 60)
{
Console.WriteLine("C");
}
else
{
Console.WriteLine("D");
}预期输出:
A把 score 分别改成 85、70、40,观察走到哪个分支。
例子 3:输入成绩并校验范围
Console.Write("成绩: ");
string? input = Console.ReadLine();
if (!int.TryParse(input, out int score))
{
Console.WriteLine("成绩必须是整数");
return;
}
if (score < 0 || score > 100)
{
Console.WriteLine("成绩必须在 0 到 100 之间");
return;
}
if (score >= 60)
{
Console.WriteLine("通过");
}
else
{
Console.WriteLine("未通过");
}要分别测试:
输入 80:输出 通过
输入 40:输出 未通过
输入 abc:输出 成绩必须是整数
输入 120:输出 成绩必须在 0 到 100 之间例子 4:for 循环打印 1 到 5
for (int i = 1; i <= 5; i++)
{
Console.WriteLine(i);
}预期输出:
1
2
3
4
5例子 5:用 continue 跳过奇数
for (int i = 1; i <= 10; i++)
{
if (i % 2 != 0)
{
continue;
}
Console.WriteLine(i);
}预期输出:
2
4
6
8
10% 是取余数。i % 2 != 0 表示不能被 2 整除,也就是奇数。
例子 6:foreach 遍历成绩列表
var scores = new List<int> { 95, 58, 76, 40, 88 };
int passedCount = 0;
foreach (int score in scores)
{
if (score >= 60)
{
Console.WriteLine($"{score} 通过");
passedCount++;
}
else
{
Console.WriteLine($"{score} 未通过");
}
}
Console.WriteLine($"通过人数: {passedCount}");这个例子把集合、循环、判断放在一起。passedCount++ 表示通过人数加 1。
例子 7:菜单循环
while (true)
{
Console.WriteLine("1. 打印当前时间");
Console.WriteLine("2. 打印欢迎语");
Console.WriteLine("0. 退出");
Console.Write("请选择: ");
string? choice = Console.ReadLine();
if (choice == "1")
{
Console.WriteLine(DateTime.Now);
}
else if (choice == "2")
{
Console.WriteLine("欢迎学习 C#");
}
else if (choice == "0")
{
Console.WriteLine("程序结束");
break;
}
else
{
Console.WriteLine("未知选项");
}
}这个例子要重点看:只要没有输入 0,程序就会回到菜单。
例子 8:用 switch 写菜单
Console.WriteLine("1. 添加商品");
Console.WriteLine("2. 查看商品");
Console.WriteLine("0. 退出");
Console.Write("请选择: ");
string? choice = Console.ReadLine();
switch (choice)
{
case "1":
Console.WriteLine("执行添加商品");
break;
case "2":
Console.WriteLine("执行查看商品");
break;
case "0":
Console.WriteLine("退出程序");
break;
default:
Console.WriteLine("未知选项");
break;
}固定选项较多时,switch 通常比很多个 else if 更清楚。
例子 9:跳过错误数据并统计总数
var scoreTexts = new List<string> { "95", "abc", "58", "120", "76" };
int validCount = 0;
int invalidCount = 0;
int totalScore = 0;
foreach (string scoreText in scoreTexts)
{
if (!int.TryParse(scoreText, out int score))
{
Console.WriteLine($"{scoreText} 不是整数,跳过");
invalidCount++;
continue;
}
if (score < 0 || score > 100)
{
Console.WriteLine($"{score} 超出范围,跳过");
invalidCount++;
continue;
}
validCount++;
totalScore += score;
Console.WriteLine($"{score} 已计入统计");
}
Console.WriteLine($"有效数据: {validCount}");
Console.WriteLine($"错误数据: {invalidCount}");
Console.WriteLine($"有效总分: {totalScore}");预期输出:
95 已计入统计
abc 不是整数,跳过
58 已计入统计
120 超出范围,跳过
76 已计入统计
有效数据: 3
错误数据: 2
有效总分: 229这里的关键是 continue:遇到错误数据时跳过本轮,不影响后面的数据继续处理。
常见错误和修法
| 错误 | 为什么错 | 修法 |
|---|---|---|
把 = 当成判断 | = 是赋值,不是比较 | 判断相等用 == |
if 后面条件不是布尔值 | C# 条件必须是 true 或 false | 写成 score >= 60 这类表达式 |
| 多个条件顺序写反 | 前面的条件会先匹配 | 范围判断从更严格的条件开始 |
| 循环忘记改变变量 | while 可能无限循环 | 每轮更新计数器或读取新输入 |
break 和 continue 混用 | 一个结束循环,一个跳过本轮 | 先写注释说明想结束还是跳过 |
每日小测
做完本页后,用这 5 题检查是否真的掌握。
1. 判断题
本页的目标不是只把代码运行起来,还要能说清楚“为什么这样写”。
答案:对。能运行只是第一步,能解释原理、常用操作和常见错误,才说明本页内容进入了可复用能力。
2. 填空题
本页主题是:控制流。今天至少要掌握的 3 个点是:
1. 程序为什么需要条件判断和循环
2. `if / else if / else` 的执行顺序
3. 比较运算符和逻辑运算符怎么读答案:以上 3 点必须能用自己的代码跑通,不能只停留在阅读。
3. 流程题
遇到本页相关功能时,先按什么顺序处理?
答案:先看完整例子,确认最终效果;再读原理和名词;然后跟着第三部分从空项目敲代码;最后对照作业答案检查。
4. 找错误题
如果本页代码运行失败,第一步应该做什么?
答案:先看终端或 IDE 里的第一条错误,找到文件名和行号;不要同时改很多地方。再回到本页的“常见错误和修法”表格,对照错误类型逐项排查。
5. 改需求题
在本页完整例子跑通后,至少改一个小需求。
可选改法:
- 改一个字段名称。
- 多加一个校验条件。
- 多输出一行结果。
- 把固定数据改成用户输入。
- 把一次处理改成多条数据处理。
答案标准:修改后能重新运行,并能说明这次修改影响了哪一段逻辑。重点检查:程序为什么需要条件判断和循环。
上位机专项练习
上位机需要判断是否报警、根据状态码显示文字,还要循环读取多个采集值。控制流就是这些功能的基础。
下面 3 个例子都要亲手敲。先运行原代码,再完成每个例子后面的改动任务。
专项例子 1:判断温度是否报警
double temperature = 82.5;
double alarmLimit = 80.0;
if (temperature >= alarmLimit)
{
Console.WriteLine("高温报警");
}
else
{
Console.WriteLine("温度正常");
}运行结果或界面效果:
高温报警改动任务: 把 temperature 改成 79.9,再运行。
专项例子 2:把设备状态码翻译成文字
int statusCode = 2;
string statusText = statusCode switch
{
0 => "离线",
1 => "运行",
2 => "报警",
_ => "未知"
};
Console.WriteLine(statusText);运行结果或界面效果:
报警改动任务: 分别测试 0、1、9 三个状态码。
专项例子 3:循环显示三次采集值
double[] values = { 20.1, 20.5, 21.0 };
foreach (double value in values)
{
Console.WriteLine($"采集值: {value:F1}");
}运行结果或界面效果:
采集值: 20.1
采集值: 20.5
采集值: 21.0改动任务: 增加一个 22.3,再观察循环次数。
第四部分:作业完整答案
这一部分给出当天作业的完整答案。先按第三部分做一遍,再用这里检查。
作业要求
写一个成绩等级程序,要求:
- 反复显示菜单。
- 输入
1时,录入一个成绩并输出等级。 - 输入
0时,退出程序。 - 成绩必须是整数。
- 成绩必须在 0 到 100 之间。
- 等级规则:
90-100:优秀80-89:良好60-79:通过0-59:未通过
作业拆解
按这个顺序写:
- 先写
while (true)菜单循环。 - 在循环里读取菜单选择。
- 选择
0时用break退出。 - 选择
1时读取成绩。 - 用
TryParse校验整数。 - 用范围判断拦住小于 0 或大于 100。
- 用
if / else if / else判断等级。
完整代码
while (true)
{
Console.WriteLine("==== 成绩菜单 ====");
Console.WriteLine("1. 输入成绩并判断等级");
Console.WriteLine("0. 退出");
Console.Write("请选择: ");
string? choice = Console.ReadLine();
if (choice == "0")
{
Console.WriteLine("程序结束");
break;
}
if (choice != "1")
{
Console.WriteLine("未知选项");
continue;
}
Console.Write("请输入成绩: ");
string? input = Console.ReadLine();
if (!int.TryParse(input, out int score))
{
Console.WriteLine("成绩必须是整数");
continue;
}
if (score < 0 || score > 100)
{
Console.WriteLine("成绩必须在 0 到 100 之间");
continue;
}
string level;
if (score >= 90)
{
level = "优秀";
}
else if (score >= 80)
{
level = "良好";
}
else if (score >= 60)
{
level = "通过";
}
else
{
level = "未通过";
}
Console.WriteLine($"成绩: {score}");
Console.WriteLine($"等级: {level}");
}正确运行结果
输入 1,再输入 95:
==== 成绩菜单 ====
1. 输入成绩并判断等级
0. 退出
请选择: 1
请输入成绩: 95
成绩: 95
等级: 优秀输入 1,再输入 abc:
请输入成绩: abc
成绩必须是整数输入 0:
程序结束你要能说清楚这 7 件事
if后面的条件最终必须是true或false。else if会从上往下检查,顺序会影响结果。&&表示两个条件都要成立。||表示至少一个条件成立。for适合次数明确的重复。while适合不知道要重复几次的流程。continue是跳过本轮,break是结束循环,return是结束当前流程。
加强作业:批量成绩统计
要求:
- 给定一组成绩文本。
- 能转成整数并且在 0 到 100 之间的成绩才参与统计。
- 错误格式和超出范围的数据要跳过。
- 输出有效数量、错误数量、通过人数、总分和平均分。
完整答案:
var scoreTexts = new List<string> { "95", "abc", "58", "120", "76", "-1", "88" };
int validCount = 0;
int invalidCount = 0;
int passedCount = 0;
int totalScore = 0;
foreach (string scoreText in scoreTexts)
{
if (!int.TryParse(scoreText, out int score))
{
Console.WriteLine($"{scoreText} 不是整数,跳过");
invalidCount++;
continue;
}
if (score < 0 || score > 100)
{
Console.WriteLine($"{score} 不在 0 到 100 之间,跳过");
invalidCount++;
continue;
}
validCount++;
totalScore += score;
if (score >= 60)
{
passedCount++;
}
}
if (validCount == 0)
{
Console.WriteLine("没有有效成绩");
return;
}
decimal average = (decimal)totalScore / validCount;
Console.WriteLine($"有效数量: {validCount}");
Console.WriteLine($"错误数量: {invalidCount}");
Console.WriteLine($"通过人数: {passedCount}");
Console.WriteLine($"总分: {totalScore}");
Console.WriteLine($"平均分: {average:F2}");正确输出:
abc 不是整数,跳过
120 不在 0 到 100 之间,跳过
-1 不在 0 到 100 之间,跳过
有效数量: 4
错误数量: 3
通过人数: 3
总分: 317
平均分: 79.25这里用到的常用操作:
| 写法 | 作用 |
|---|---|
foreach | 逐个处理成绩文本 |
int.TryParse | 安全转换整数 |
continue | 跳过错误数据 |
validCount++ | 有效数量加 1 |
totalScore += score | 累加总分 |
(decimal)totalScore / validCount | 转成小数后再算平均分 |
{average:F2} | 平均分保留两位小数 |