Loops(循环)
循环用于重复执行一段代码。Dart 提供 for、for-in、while 和 do-while:已知重复次数时通常使用 for,逐个读取可迭代对象时使用 for-in,根据条件决定是否继续时使用 while 或 do-while。
学习循环时,需要重点掌握以下问题:
- 如何选择合适的循环形式?
- 循环条件在什么时候检查?
break和continue分别会改变哪一步?- 嵌套循环中如何明确跳出或继续外层循环?
使用 for 控制重复次数
经典 for 循环由初始化、条件和更新三部分组成:
void main() {
for (var index = 0; index < 3; index++) {
print('lesson $index');
}
}
执行顺序是:
var index = 0只在进入循环时执行一次。- 每轮开始前检查
index < 3,结果必须是bool。 - 条件为
true时执行循环体。 - 执行
index++,然后再次检查条件。
因此上例依次输出 lesson 0、lesson 1 和 lesson 2。循环变量 index 只在这个 for 循环及其循环体内可见。
初始化和更新部分可以省略。省略条件会形成无限循环,必须通过其他方式结束:
void main() {
var count = 0;
for (;;) {
if (count == 3) {
break;
}
print(count);
count++;
}
}
只有在退出条件清晰时才应使用无限循环,否则代码容易无法结束。
使用 for-in 遍历元素
for-in 按迭代顺序逐个读取 Iterable 中的元素。List 和 Set 都可以直接使用这种写法:
void main() {
const topics = ['variables', 'types', 'loops'];
for (final topic in topics) {
print(topic);
}
}
如果循环体不需要给当前元素重新赋值,优先使用 final。for-in 适合只关心元素、不关心索引的场景。
需要同时读取列表索引和值时,可以遍历 indexed。它从 Dart 3.0 开始可用,并为每个元素产生一个 (int, T) record:
void main() {
const topics = ['variables', 'types', 'loops'];
for (final (index, topic) in topics.indexed) {
print('$index: $topic');
}
}
遍历 Map 时,通常遍历 entries,这样可以同时取得键和值:
void main() {
const progress = <String, int>{
'variables': 100,
'loops': 60,
};
for (final entry in progress.entries) {
print('${entry.key}: ${entry.value}%');
}
}
如果需要修改集合本身,应谨慎处理。在遍历可增长集合的过程中增加或删除元素,通常会抛出 ConcurrentModificationError。可以先创建副本再遍历:
void main() {
final scores = <int>[45, 80, 55];
for (final score in scores.toList()) {
if (score < 60) {
scores.remove(score);
}
}
print(scores); // [80]
}
使用 while 按条件重复
while 在每轮开始前检查条件。第一次检查就是 false 时,循环体一次也不会执行:
void main() {
var attempts = 0;
while (attempts < 3) {
print('attempt ${attempts + 1}');
attempts++;
}
}
使用 while 时,要确保循环体最终能够改变条件。上例中的 attempts++ 如果缺失,条件会一直为 true,循环也不会结束。
Dart 不会把数字、字符串或对象自动转换成布尔值,循环条件必须是 bool:
void main() {
var remaining = 2;
// while (remaining) {} // 编译错误:int 不是 bool
while (remaining > 0) {
print(remaining);
remaining--;
}
}
使用 do-while 至少执行一次
do-while 先执行循环体,再检查条件,因此循环体至少会运行一次:
void main() {
var number = 5;
do {
print(number);
number++;
} while (number < 5);
}
虽然 number < 5 第一次检查时就是 false,程序仍会输出一次 5。当业务流程要求“先执行,再决定是否重试”时,do-while 比普通 while 更直接。
使用 break 提前结束循环
break 会立即结束它所在的最近一层循环,程序从循环后的语句继续执行:
void main() {
const scores = [45, 72, 91, 58];
for (final score in scores) {
if (score >= 90) {
print('first excellent score: $score');
break;
}
}
}
上例找到第一个不低于 90 的分数后,不再检查剩余元素。
使用 continue 跳过当前轮次
continue 会停止当前轮循环体的剩余部分,并进入下一轮:
void main() {
const scores = [45, 72, 91, 58];
for (final score in scores) {
if (score < 60) {
continue;
}
print('passed: $score');
}
}
在经典 for 循环中,执行 continue 后仍会先运行更新部分,再检查下一轮条件。在 while 中则会直接回到条件检查,因此不要把必须执行的状态更新放在可能被 continue 跳过的位置。
void main() {
var number = 0;
while (number < 4) {
number++;
if (number.isOdd) {
continue;
}
print(number); // 2、4
}
}
使用标签控制嵌套循环
在嵌套循环中,普通 break 和 continue 只影响最近一层循环。Dart 可以为循环添加标签,明确指定要控制的外层循环:
void main() {
outerLoop:
for (var row = 0; row < 3; row++) {
for (var column = 0; column < 3; column++) {
if (row == 1 && column == 1) {
break outerLoop;
}
print('($row, $column)');
}
}
}
break outerLoop 会直接结束两层循环。continue outerLoop 则会结束当前外层轮次,进入外层循环的下一轮。标签能表达复杂控制流,但频繁使用会增加阅读难度;可以拆分函数时,通常应优先让每个函数只负责一层逻辑。
与 forEach() 的区别
Iterable.forEach() 也能依次处理元素,但它接收的是回调函数:
void main() {
const topics = ['variables', 'types', 'loops'];
topics.forEach((topic) {
print(topic);
});
}
回调中的 return 只会结束当前回调,不会像 break 一样停止整个遍历;回调内也不能使用循环的 break 或 continue。需要提前结束、跳过元素或使用 await 顺序执行异步操作时,通常优先使用 for-in。
TypeScript 对比
Dart 与 TypeScript 都提供经典 for、for-of/for-in、while、do-while、break、continue 和标签,但有两个容易混淆的差异:
| 对比项 | Dart | TypeScript |
|---|---|---|
| 遍历元素 | for (final item in items) | for (const item of items) |
| 条件类型 | 必须是 bool | 会按 JavaScript 规则转换为真值或假值 |
TypeScript 的 for-in 遍历对象的可枚举属性名,不能直接类比 Dart 的 for-in。遍历数组元素时,TypeScript 使用 for-of:
const topics = ['variables', 'types', 'loops'];
for (const topic of topics) {
console.log(topic);
}
两种语言中的 break 和 continue 基本作用相同。真正需要记忆的是:Dart 的 in 表示从 Iterable 中逐个取得元素,而 TypeScript 通常用 of 表达这个意图。
选择循环的简单规则
| 需求 | 推荐写法 |
|---|---|
| 已知开始值、结束条件和更新规则 | for |
逐个读取 Iterable 的元素 | for-in |
| 每轮开始前判断是否继续 | while |
| 循环体必须至少执行一次 | do-while |
| 找到目标后立即结束 | 在循环中使用 break |
| 忽略当前元素并处理下一个 | 在循环中使用 continue |
优先选择最能直接表达意图的循环形式。无论使用哪一种,都要保证退出条件能够到达,并避免在遍历期间直接改变同一个集合的长度。