Ziglings 笔记 63: 带标签的循环 (Labeled Loops)
指哪打哪
在 Ziglings 的第 63 个练习中,我们解决了一个经典的编程难题:如何优雅地跳出多层嵌套循环?
在制作食堂菜单匹配系统时,我们需要遍历每一个食物,再遍历它需要的每一个配料。这是一个典型的双重循环结构。一旦我们在内层发现“缺货”,就需要立即放弃当前食物,转而检查下一个食物(操作外层循环)。
挑战:食堂点餐系统
任务是根据顾客想要的配料(wanted_ingredients),在菜单(menu)中找到匹配的食物。
- 如果缺少任何一项必需配料,就跳过该食物。
- 如果找到了所有配料都齐全的食物,就返回该食物。
- 如果都没找到,返回默认的 “Mac & Cheese”。
解决方案
使用 Labeled Loops,我们可以给外层循环起个名字 food_loop,然后在内层直接引用它:
const std = @import("std");
// ... 省略结构体定义 ...
pub fn main() void {
const wanted_ingredients = [_]u8{ 0, 3 }; // Chili, Cheese
// 1. 给外层循环加标签: food_loop
const meal = food_loop: for (menu) |food| {
for (food.requires, 0..) |required, required_ingredient| {
if (!required) continue;
// 检查配料是否存在...
const found = for (wanted_ingredients) |want_it| {
if (required_ingredient == want_it) break true;
} else false;
// 2. 定向 Continue
// 如果没找到配料,直接跳过外层循环的当前迭代
// "这个菜做不了,看下一个菜吧"
if (!found) continue :food_loop;
}
// 3. 定向 Break 并返回值
// 如果代码跑到这里,说明所有配料都齐了。
// "找到了!就是这个菜!带上它离开循环!"
break :food_loop food;
} else menu[0]; // 4. 默认值
// 如果循环跑完了都没触发 break,说明没找到合适的,返回默认菜。
std.debug.print("Enjoy your {s}!\n", .{meal.name});
}
核心知识点总结
1. 消除 Flag 变量
在没有标签的语言(如 C)中,我们通常需要这样做:
bool possible = true;
for (ingredients) {
if (missing) {
possible = false; // 先标记
break; // 跳出内层
}
}
if (!possible) continue; // 在外层检查标记
Zig 的 continue :label 让我们可以直接“传送”到外层循环的下一次迭代,逻辑更加线性、清晰。
2. 代码块也是表达式
不仅是循环,任何用 { ... } 包裹的代码块都可以加标签,并使用 break :label value 来返回值。这在需要复杂的初始化逻辑(Complex Initialization)时非常有用,可以避免声明可变的 var 变量。
3. 挑战题解答
练习最后的 Challenge 问如何去掉 found 变量。我们可以直接把 if 逻辑嵌入到查找配料的循环中:
// 这种写法利用了 for..else 结构
for (wanted_ingredients) |want_it| {
if (required_ingredient == want_it) break; // 找到了,跳出查找循环
} else {
// 没找到(查找循环自然结束)
// 既然缺配料,那就直接跳过外层食物循环
continue :food_loop;
}
后续预告:我们已经掌握了大部分基础语法。接下来的练习将进入 Formatting (格式化)。Zig 的打印功能非常强大,不仅能打印字符串,还能控制对齐、进制、浮点精度等。下一篇我们将深入研究 {} 占位符背后的秘密。