📘 第03课:控制流与函数

📋 本课目录if表达式while循环for循环switch表达式标签与break函数基础defer与errdefer编译期函数课后练习

if表达式

Zig中if不仅是语句,更是表达式——它有返回值:

const std = @import("std");

pub fn main() !void {
    // if作为表达式
    const x: i32 = 42;
    const result = if (x > 0) "正数" else "非正数";
    std.debug.print("x是{s}\n", .{result});

    // 可选值解包
    var maybe_value: ?i32 = 100;
    if (maybe_value) |v| {
        std.debug.print("值: {}\n", .{v});
    } else {
        std.debug.print("值为null\n", .{});
    }

    // 可选指针捕获
    var num: i32 = 42;
    var opt_ptr: ?*i32 = #
    if (opt_ptr) |ptr| { ptr.* += 1; }

    // 错误联合值解包
    const err_result: error{DivByZero}!i32 = 42;
    if (err_result) |val| {
        std.debug.print("成功: {}\n", .{val});
    } else |err| {
        std.debug.print("错误: {}\n", .{err});
    }
    std.debug.print("num = {}\n", .{num});
}

性能分析

if表达式编译后与三元运算符等价,零额外开销。可选值解包通过分支预测优化,null检查被内联到条件跳转。Debug模式有完整安全检查,ReleaseFast自动消除。

while循环

const std = @import("std");

pub fn main() !void {
    // while + continue表达式
    var i: usize = 0;
    while (i < 5) : (i += 1) {
        std.debug.print("i = {}\n", .{i});
    }

    // continue时也执行continue表达式
    i = 0;
    while (i < 10) : (i += 1) {
        if (i % 2 == 0) continue;
        std.debug.print("奇数: {}\n", .{i});
    }

    // 嵌套循环与标签break
    var outer: usize = 0;
    outer_loop: while (outer < 3) : (outer += 1) {
        var inner: usize = 0;
        while (inner < 3) : (inner += 1) {
            if (outer == 1 and inner == 1) break :outer_loop;
            std.debug.print("({},{} ) ", .{ outer, inner });
        }
    }

    // 可选值迭代
    var items = [5]?i32{ 10, null, 30, null, 50 };
    var idx: usize = 0;
    while (idx < items.len) : (idx += 1) {
        if (items[idx]) |val| {
            std.debug.print("有效值[{}]: {}\n", .{ idx, val });
        }
    }
}

性能分析

while编译为原生条件跳转,continue表达式优化为计数器自增。标签break为直接跳转,无开销。Zig没有C的for(;;),用while(true)替代。

for循环

const std = @import("std");

pub fn main() !void {
    const nums = [_]i32{ 10, 20, 30, 40, 50 };

    // 基本遍历
    for (nums) |val| std.debug.print("{} ", .{val});

    // 带索引
    for (nums, 0..) |val, i| std.debug.print("[{}]={} ", .{ i, val });

    // 引用捕获修改
    var arr = [_]i32{ 1, 2, 3, 4, 5 };
    for (&arr) |*elem| elem.* *= 2;

    // 多序列并行
    const a = [_]i32{ 1, 2, 3 };
    const b = [_]i32{ 4, 5, 6 };
    for (a, b, 0..) |va, vb, i| {
        std.debug.print("[{}] {}+{}={}\n", .{ i, va, vb, va + vb });
    }
}
💡 for vs while:for只能遍历数组/切片,while更灵活。for编译后与while等价。Zig没有C风格for(;;)——用while(true)替代。

switch表达式

const std = @import("std");
const Direction = enum { north, south, east, west };

pub fn main() !void {
    // 枚举switch——编译器强制穷举
    const dir = Direction.east;
    const emoji = switch (dir) {
        .north => "⬆️", .south => "⬇️",
        .east => "➡️", .west => "⬅️",
    };

    // 整数范围匹配
    const score: i32 = 85;
    const grade = switch (score) {
        90...100 => "A", 80...89 => "B",
        70...79 => "C", 60...69 => "D",
        else => "F",
    };

    // 多值匹配
    const ch: u8 = 'e';
    switch (ch) {
        'a','e','i','o','u' => std.debug.print("元音\n",.{}),
        'A'...'Z','a'...'z' => std.debug.print("辅音\n",.{}),
        else => std.debug.print("其他\n",.{}),
    }

    // 标签switch
    var counter: u32 = 5;
    const desc = switch (counter) {
        0...9 => blk: {
            counter += 1;
            break :blk "个位数";
        },
        else => "多位数",
    };
    _ = emoji; _ = grade; _ = desc;
}

性能分析

switch编译为跳转表(小范围整数)或二分查找(稀疏值),O(1)。穷举检查编译期完成,零运行时开销。比C的switch更强:表达式、穷举、范围匹配。

标签与break

const std = @import("std");

pub fn main() !void {
    // 标签块——break返回值
    const result = blk: {
        const x = 10;
        const y = 20;
        break :blk x + y;
    };

    // 条件标签块
    const value: i32 = 42;
    const msg = check: {
        if (value > 100) break :check "大";
        if (value > 50) break :check "中";
        break :check "小";
    };

    // 嵌套循环标签break
    outer: for (0..3) |i| {
        for (0..3) |j| {
            if (i == 1 and j == 1) break :outer;
        }
    }
    std.debug.print("结果: {}, 判断: {s}\n", .{result, msg});
}

函数基础

const std = @import("std");

fn add(a: i32, b: i32) i32 { return a + b; }

fn divide(a: f64, b: f64) !f64 {
    if (b == 0.0) return error.DivByZero;
    return a / b;
}

// comptime泛型
fn max(comptime T: type, a: T, b: T) T {
    return if (a > b) a else b;
}

// 递归
fn fibonacci(n: u32) u64 {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

// 多返回值
const DivResult = struct { quotient: i32, remainder: i32 };
fn divmod(a: i32, b: i32) DivResult {
    return .{ .quotient = @divTrunc(a, b), .remainder = @rem(a, b) };
}

pub fn main() !void {
    std.debug.print("add(3,4)={}\n", .{add(3, 4)});
    const r = try divide(10.0, 3.0);
    std.debug.print("10/3={d:.2}\n", .{r});
    std.debug.print("max(i32)={} max(f64)={d:.1}\n",
        .{max(i32, 42, 17), max(f64, 3.14, 2.71)});
    std.debug.print("fib(10)={}\n", .{fibonacci(10)});
    const dm = divmod(17, 5);
    std.debug.print("17÷5={}余{}\n", .{dm.quotient, dm.remainder});
}

参数传递规则

defer与errdefer

const std = @import("std");

pub fn main() !void {
    // defer LIFO顺序
    {
        defer std.debug.print("A\n", .{});
        defer std.debug.print("B\n", .{});
        defer std.debug.print("C\n", .{});
        // 输出: C, B, A
    }

    // 文件资源保证
    const file = try std.fs.cwd().openFile("/etc/hostname", .{});
    defer file.close();
    var buf: [256]u8 = undefined;
    const n = try file.readAll(&buf);
    std.debug.print("读取{}字节\n", .{n});
}

// errdefer——错误路径清理
fn allocAndInit(allocator: std.mem.Allocator) !*i32 {
    const ptr = try allocator.create(i32);
    errdefer allocator.destroy(ptr);
    ptr.* = 42;
    return ptr;
}

编译期函数

const std = @import("std");

// comptime泛型——返回类型
fn Matrix(comptime T: type, comptime w: comptime_int,
           comptime h: comptime_int) type {
    return [h][w]T;
}

// 编译期字符串哈希
fn comptimeHash(comptime str: []const u8) u32 {
    comptime var hash: u32 = 5381;
    for (str) |c| hash = ((hash << 5) + hash) + c;
    return hash;
}

pub fn main() !void {
    const Mat3 = Matrix(f32, 3, 3);
    var m: Mat3 = undefined;
    m[0] = .{1.0,0.0,0.0};
    m[1] = .{0.0,1.0,0.0};
    m[2] = .{0.0,0.0,1.0};

    const h = comptimeHash("hello");
    std.debug.print("hash={}\n", .{h});
}

课后练习

📝 练习1:FizzBuzz

1-100输出,3倍数Fizz,5倍数Buzz,15倍数FizzBuzz

📝 练习2:二分查找

实现泛型二分查找函数,使用comptime类型参数

📝 练习3:资源管理

用defer实现一个简单的文件读取函数,确保文件总是关闭

📝 练习4:编译期计算

用comptime计算1到100的和,编译期完成

📝 练习5:标签循环

找出第一个i*j>50的组合,使用标签break

🏆 成就解锁:控制流大师