Ziglings 笔记 76: 哨兵 (Sentinels) - 寻找结束的标志

到此为止

在 Ziglings 的第 76 个练习中,我们学习了 Sentinels (哨兵)

在很多系统中,我们不知道数据到底有多长,但我们知道数据“以什么结束”。最著名的例子就是 C 语言的字符串(以 0 结尾)。Zig 原生支持这种模式,并且不仅限于字符串,任何类型的数组都可以有哨兵。

挑战:同源不同相

我们有一个数组 nums,我们在其中间插入了一个 0

  • nums 是一个定长数组,以 0 为哨兵。
  • ptr 是一个指向 nums多项指针,也以 0 为哨兵。

当我们分别打印它们时,会发生什么?

解决方案

我们需要完善 printSequence 函数。主要修复点是添加打印时的空格,以及理解遍历逻辑。

const print = @import("std").debug.print;
const sentinel = @import("std").meta.sentinel;

pub fn main() void {
    // 定义一个带哨兵的数组:长度为 6,以 0 结尾
    // 内存布局:1, 2, 3, 4, 5, 6, 0 (编译器自动维护最后的 0)
    var nums = [_:0]u32{ 1, 2, 3, 4, 5, 6 };

    // 定义一个带哨兵的指针
    const ptr: [*:0]u32 = &nums;

    // 捣乱:把中间的值改成 0
    // 现在的内存:1, 2, 3, 0, 5, 6, 0
    nums[3] = 0;

    // 打印数组:输出 1 2 3 0 5 6
    // 打印指针:输出 1 2 3
    printSequence(nums);
    printSequence(ptr);

    print("\n", .{});
}

fn printSequence(my_seq: anytype) void {
    const my_typeinfo = @typeInfo(@TypeOf(my_seq));

    switch (my_typeinfo) {
        .array => {
            print("Array:", .{});
            // 数组遍历依赖的是长度 (len)
            // 尽管中间有 0,循环依然会走完所有 6 个元素
            for (my_seq) |s| {
                print(" {}", .{s}); // 添加空格以便阅读
            }
        },
        .pointer => {
            // 获取哨兵值 (对于 [*:0]u32 来说是 0)
            const my_sentinel = sentinel(@TypeOf(my_seq));
            print("Many-item pointer:", .{});

            var i: usize = 0;
            // 指针遍历依赖的是内容
            // 一旦遇到哨兵值,循环立即终止
            while (my_seq[i] != my_sentinel) {
                print(" {}", .{my_seq[i]});
                i += 1;
            }
        },
        else => unreachable,
    }
    print(". ", .{});
}

核心知识点总结

1. 数组的“上帝视角”

定长数组 [N:S]T 在编译期就知道自己有多长。 当你使用 for 循环遍历它时,编译器使用的是索引 0..N。它并不在乎内容是什么,所以它会跨过中间的哨兵值,直到打印完所有分配的元素。

2. 指针的“盲人摸象”

哨兵指针 [*:S]T 丢失了长度信息。 它操作起来就像 C 语言的 while (*str != '\0')。它必须逐个检查元素的值。一旦遇到与哨兵相等的值(本例中为 0),它就认为数据结束了。

3. 类型反射的应用

代码中的 const my_sentinel = sentinel(@TypeOf(my_seq)); 展示了 Zig 标准库的反射能力。我们不需要硬编码 0,而是让编译器去查询类型的定义:“嘿,这个指针的终止符是什么?”。这让函数变得通用。


后续预告:我们已经学习了哨兵。接下来的练习将进入标准库最常用的部分——内存分配器 (Allocators)。在 Zig 中,没有隐式的 mallocnew,你必须显式地管理内存。下一篇我们将学习 std.heap.page_allocator