Ziglings 笔记 52: 切片 (Slices) - 数组的万能视图

摆脱长度的束缚

在 Ziglings 的第 52 个练习中,我们终于遇到了 切片 (Slices)

在此之前,数组让我们很头疼。因为在 Zig 中,[4]u8[5]u8 是完全不同的两种数据类型。这意味着如果你写了一个函数打印数组,你必须把数组长度硬编码在参数里,这简直是噩梦。

Zig 的切片完美解决了这个问题。它提供了一个动态的窗口,让我们查看数组的一部分或全部。

挑战:分发扑克牌

我们有一副牌(数组 cards),包含 8 张牌。 我们需要把前 4 张分给 hand1,剩下的分给 hand2,并编写一个通用的函数来打印它们。

解决方案

这是使用切片的实现:

const std = @import("std");

pub fn main() void {
    var cards = [8]u8{ 'A', '4', 'K', '8', '5', '2', 'Q', 'J' };

    // 1. 创建切片
    // 语法:数组[开始索引 .. 结束索引] (左闭右开)
    // hand1 的类型是 []u8 (注意方括号里没有数字)
    const hand1: []u8 = cards[0..4]; 
    
    // 省略结束索引,表示“直到末尾”
    const hand2: []u8 = cards[4..];

    std.debug.print("Hand1: ", .{});
    printHand(hand1);

    std.debug.print("Hand2: ", .{});
    printHand(hand2);
}

// 2. 接收切片的函数
// 这里的参数类型是 []const u8。
// 这表示它可以接收任何长度的 u8 切片,且承诺不修改里面的数据。
fn printHand(hand: []const u8) void {
    // 切片也可以像数组一样直接用于 for 循环
    for (hand) |h| {
        std.debug.print("{u} ", .{h});
    }
    std.debug.print("\n", .{});
}

核心知识点总结

1. 切片是什么?

切片不是数组的拷贝,它只是数组的一个视图 (View)。 在底层,切片就是一个包含两样东西的小结构体:

  1. 指针:指向数据的起始位置。
  2. 长度:切片包含多少个元素。

因为它是指针,所以修改切片里的数据(如 hand1[0] = 'X'),原始数组 cards 里的数据也会变!

2. 动态长度

注意类型写法的区别:

  • [8]u8:长度为 8 的数组(编译期确定)。
  • []u8:未知长度的切片(运行期确定)。

我们在编写通用函数(如字符串处理、缓冲区处理)时,几乎总是使用切片 []T 作为参数。

3. 安全性

虽然切片底层是指针,但 Zig 会对切片访问进行边界检查。如果你试图访问 hand1[10],程序在运行时会安全地 Panic,而不是像 C 语言那样读取非法内存。


后续预告:我们已经学会了切片。但是切片和“多项指针” ([*]T) 之间有什么关系?它们看起来很像,但安全性完全不同。下一篇我们将深入探讨切片的更多用法。