承接前文
在前两篇文章中,我们学习了 Zig 的基础知识:变量与数据类型、数组与字符串,以及使用 defer 进行内存管理。掌握了这些基础概念后,今天我们就来探索 Zig 的**控制流(Control Flow)**机制。
控制流是编程语言的核心,它决定了程序的执行路径。Zig 提供了多种控制流结构:
- if - 条件判断
- switch - 多分支选择
- while - 条件循环
- for - 迭代循环
与许多现代语言不同,Zig 的控制流设计遵循"显式优于隐式"的哲学,没有隐式的类型转换或意外的行为。让我们逐一深入了解。
if 表达式
基本用法
Zig 的 if 是一个表达式,这意味着它可以返回值。基本条件判断如下:
1 2 3 4 5 6 7 8 9 10 11
| const std = @import("std");
pub fn main() void { const age: u8 = 18; if (age >= 18) { std.debug.print("成年人\n", .{}); } else { std.debug.print("未成年人\n", .{}); } }
|
if 作为表达式
既然 if 是表达式,我们就可以用它来为变量赋值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const std = @import("std");
pub fn main() void { const score: u8 = 85; // if 表达式返回值 const grade = if (score >= 90) { 'A' } else if (score >= 80) { 'B' } else if (score >= 60) { 'C' } else { 'D' }; std.debug.print("等级: {c}\n", .{grade}); }
|
注意:作为表达式使用时,if 的所有分支必须返回相同类型的值。
可选类型与 if
Zig 没有 null,而是使用可选类型(Optional Types)?T 来表示可能不存在的值。结合 if 可以安全地处理可选值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const std = @import("std");
pub fn main() void { const maybe_value: ?i32 = 42; // 使用 if 解构可选类型 if (maybe_value) |value| { std.debug.print("值存在: {d}\n", .{value}); } else { std.debug.print("值不存在\n", .{}); } // 空值的情况 const empty: ?i32 = null; if (empty) |value| { std.debug.print("值: {d}\n", .{value}); } else { std.debug.print("空值\n", .{}); } }
|
使用 if (optional) |value| { ... } 的语法,当可选值存在时,value 会被赋值为解包后的值。
错误联合类型与 if
类似地,if 也可以处理错误联合类型 !T:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const std = @import("std");
fn mayFail() !i32 { return 42; // 或者返回错误: return error.SomeError; }
pub fn main() !void { const result = mayFail(); // 使用 if 解构错误联合类型 if (result) |value| { std.debug.print("成功: {d}\n", .{value}); } else |err| { std.debug.print("错误: {s}\n", .{@errorName(err)}); } }
|
switch 表达式
基本用法
switch 是 Zig 中强大的多分支选择结构,它也是一个表达式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const std = @import("std");
pub fn main() void { const number: u8 = 3; const result = switch (number) { 1 => "一", 2 => "二", 3 => "三", 4 => "四", 5 => "五", else => "其他", }; std.debug.print("{s}\n", .{result}); }
|
重要:switch 必须覆盖所有可能的情况,因此通常需要 else 分支。
范围匹配
Zig 的 switch 支持范围表达式,使用 ... 运算符创建包含两端点的范围:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const std = @import("std");
pub fn main() void { const level: u8 = 45; const category = switch (level) { 0...25 => "初学者", 26...75 => "中级", 76...100 => "专家", else => { @panic("不支持的等级!"); }, }; std.debug.print("等级 {d} 属于: {s}\n", .{ level, category }); }
|
范围匹配也适用于字符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const std = @import("std");
fn getCharType(c: u8) []const u8 { return switch (c) { 'a'...'z' => "小写字母", 'A'...'Z' => "大写字母", '0'...'9' => "数字", else => "其他字符", }; }
pub fn main() void { std.debug.print("'a' 是 {s}\n", .{getCharType('a')}); std.debug.print("'5' 是 {s}\n", .{getCharType('5')}); std.debug.print("'@' 是 {s}\n", .{getCharType('@')}); }
|
多值匹配
可以在一个分支中匹配多个值:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const std = @import("std");
pub fn main() void { const day: u8 = 6; const dayType = switch (day) { 1, 2, 3, 4, 5 => "工作日", 6, 7 => "周末", else => "无效的日期", }; std.debug.print("星期{d} 是 {s}\n", .{ day, dayType }); }
|
switch 与枚举
switch 与枚举类型配合使用时,如果覆盖了所有枚举值,可以省略 else:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const std = @import("std");
const Color = enum { red, green, blue, };
pub fn main() void { const color = Color.green; const name = switch (color) { .red => "红色", .green => "绿色", .blue => "蓝色", // 不需要 else,因为所有枚举值都已覆盖 }; std.debug.print("颜色: {s}\n", .{name}); }
|
while 循环
基本 while 循环
while 是 Zig 中最灵活的循环结构:
1 2 3 4 5 6 7 8 9 10 11
| const std = @import("std");
pub fn main() void { var i: u8 = 0; while (i < 5) { std.debug.print("{d} ", .{i}); i += 1; } // 输出: 0 1 2 3 4 }
|
带 continue 表达式的 while
Zig 的 while 支持** continue 表达式**,在每次迭代结束时执行:
1 2 3 4 5 6 7 8 9 10 11
| const std = @import("std");
pub fn main() void { var i: u8 = 0; // 传统 for 循环的风格:while (条件) : (更新) while (i < 5) : (i += 1) { std.debug.print("{d} ", .{i}); } // 输出: 0 1 2 3 4 }
|
这种语法 while (条件) : (继续表达式) 让 while 可以模拟传统 C 风格的 for 循环。
while 与可选类型
while 也可以用于解构可选类型,实现"当值存在时继续循环"的模式:
1 2 3 4 5 6 7 8 9 10 11 12
| const std = @import("std");
pub fn main() void { var maybe_num: ?i32 = 5; // 当 maybe_num 不为 null 时继续循环 while (maybe_num) |num| { std.debug.print("{d} ", .{num}); maybe_num = if (num > 0) num - 1 else null; } // 输出: 5 4 3 2 1 0 }
|
while 与错误联合类型
类似地,while 可以处理可能返回错误的操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const std = @import("std");
var counter: i32 = 0;
fn mayReturnError() !i32 { counter += 1; if (counter > 3) return error.TooMany; return counter; }
pub fn main() !void { // 当操作成功时继续 while (mayReturnError()) |value| { std.debug.print("成功: {d}\n", .{value}); } else |err| { std.debug.print("错误: {s}\n", .{@errorName(err)}); } }
|
for 循环
基本用法
Zig 的 for 循环专门用于迭代,与 while 的职责清晰分离:
1 2 3 4 5 6 7 8 9 10 11
| const std = @import("std");
pub fn main() void { const items = [_]i32{ 10, 20, 30, 40, 50 }; // 迭代数组 for (items) |item| { std.debug.print("{d} ", .{item}); } // 输出: 10 20 30 40 50 }
|
获取索引
使用 for (items) |item, index| 可以同时获取元素和索引:
1 2 3 4 5 6 7 8 9
| const std = @import("std");
pub fn main() void { const fruits = [_][]const u8{ "苹果", "香蕉", "橙子" }; for (fruits) |fruit, i| { std.debug.print("[{d}] {s}\n", .{ i, fruit }); } }
|
迭代切片
for 循环也可以迭代切片:
1 2 3 4 5 6 7 8 9 10 11
| const std = @import("std");
pub fn main() void { const arr = [_]u8{ 1, 2, 3, 4, 5 }; const slice = arr[1..4]; // {2, 3, 4} for (slice) |item| { std.debug.print("{d} ", .{item}); } // 输出: 2 3 4 }
|
迭代字符串
迭代字符串时,我们得到的是字节:
1 2 3 4 5 6 7 8 9 10
| const std = @import("std");
pub fn main() void { const text = "Hello"; for (text) |byte| { std.debug.print("{c} ", .{byte}); } // 输出: H e l l o }
|
使用范围迭代
Zig 提供了 0..n 语法来创建范围,用于迭代指定次数:
1 2 3 4 5 6 7 8 9
| const std = @import("std");
pub fn main() void { // 迭代 0 到 4(不包含 5) for (0..5) |i| { std.debug.print("{d} ", .{i}); } // 输出: 0 1 2 3 4 }
|
注意:0..5 创建的区间是左闭右开的,包含 0 但不包含 5。
循环控制:break 与 continue
break
break 用于立即退出循环:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const std = @import("std");
pub fn main() void { var i: u8 = 0; while (true) : (i += 1) { if (i >= 5) { break; // 退出循环 } std.debug.print("{d} ", .{i}); } // 输出: 0 1 2 3 4 }
|
break 也可以带返回值(类似带标签的代码块):
1 2 3 4 5 6 7 8 9 10 11
| const std = @import("std");
pub fn main() void { const result = for (0..10) |i| { if (i * i > 30) { break i; // 返回第一个使 i*i > 30 的 i } } else 10; // 如果没有 break,返回 10 std.debug.print("结果: {d}\n", .{result}); // 输出: 6 }
|
continue
continue 用于跳过当前迭代,进入下一次循环:
1 2 3 4 5 6 7 8 9 10 11
| const std = @import("std");
pub fn main() void { for (0..10) |i| { if (i % 2 == 0) { continue; // 跳过偶数 } std.debug.print("{d} ", .{i}); } // 输出: 1 3 5 7 9 }
|
带标签的循环
与代码块一样,循环也可以添加标签,用于在嵌套循环中精确控制:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const std = @import("std");
pub fn main() void { outer: for (0..3) |i| { for (0..3) |j| { if (i * j > 2) { break :outer; // 跳出外层循环 } std.debug.print("({d},{d}) ", .{ i, j }); } } // 输出: (0,0) (0,1) (0,2) (1,0) (1,1) (1,2) (2,0) (2,1) }
|
Ziglings 练习
通过以下练习巩固控制流的知识:
练习 009-011:if 表达式
1 2 3 4 5 6 7 8 9 10
| // 练习 009:基本的 if 表达式 const std = @import("std");
pub fn main() void { const discount = true; const price: u8 = if (discount) 75 else 100; std.debug.print("价格: {d}\n", .{price}); }
|
1 2 3 4 5 6 7 8 9 10 11 12
| // 练习 010:if 与可选类型 const std = @import("std");
pub fn main() void { const maybe_num: ?u32 = 1729; if (maybe_num) |num| { std.debug.print("数字是: {d}\n", .{num}); } else { std.debug.print("没有数字\n", .{}); } }
|
练习 012-014:while 循环
1 2 3 4 5 6 7 8 9 10
| // 练习 012:带 continue 表达式的 while const std = @import("std");
pub fn main() void { var i: u8 = 0; while (i < 5) : (i += 1) { std.debug.print("{d}", .{i}); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| // 练习 013:while 与可选类型 const std = @import("std");
pub fn main() void { var n: ?u32 = 3; while (n) |value| { std.debug.print("{d}... ", .{value}); n = if (value > 0) value - 1 else null; } std.debug.print("发射!\n", .{}); } // 输出: 3... 2... 1... 0... 发射!
|
练习 015-017:for 循环
1 2 3 4 5 6 7 8 9 10
| // 练习 015:基本的 for 循环 const std = @import("std");
pub fn main() void { const animals = [_][]const u8{ "狗", "猫", "鸟" }; for (animals) |animal| { std.debug.print("{s} ", .{animal}); } }
|
1 2 3 4 5 6 7 8 9 10
| // 练习 016:带索引的 for 循环 const std = @import("std");
pub fn main() void { const nums = [_]u8{ 2, 4, 6, 8 }; for (nums) |n, i| { std.debug.print("[{d}] {d}\n", .{ i, n }); } }
|
1 2 3 4 5 6 7 8 9
| // 练习 017:范围迭代 const std = @import("std");
pub fn main() void { // 打印 1 到 5 for (1..6) |i| { std.debug.print("{d} ", .{i}); } }
|
总结
本文我们深入学习了 Zig 的控制流机制:
-
if 表达式:
- 可以作为表达式返回值
- 支持可选类型解构
if (optional) |value|
- 支持错误联合类型解构
if (result) |value| else |err|
-
switch 表达式:
- 必须覆盖所有情况
- 支持范围匹配
0...100
- 支持多值匹配
1, 2, 3
- 与枚举配合时可省略
else
-
while 循环:
- 基本形式:
while (条件) { ... }
- 带 continue 表达式:
while (条件) : (更新) { ... }
- 支持可选类型和错误联合类型解构
-
for 循环:
- 专用于迭代:
for (items) |item|
- 可获取索引:
for (items) |item, index|
- 范围迭代:
for (0..n) |i|
-
循环控制:
break:退出循环,可带返回值
continue:跳过当前迭代
- 带标签的循环:用于嵌套循环控制
Zig 的控制流设计体现了语言的核心理念:清晰、显式、无意外。每个结构都有明确的用途,没有隐式的类型转换或魔术行为。这种设计虽然代码量可能稍多,但大大降低了出错的可能性,让代码更易读、易维护。
在下一篇文章中,我们将探索 Zig 的函数定义、参数传递、错误处理等高级特性,继续我们的 Zig 学习之旅!