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)。 在底层,切片就是一个包含两样东西的小结构体:
- 指针:指向数据的起始位置。
- 长度:切片包含多少个元素。
因为它是指针,所以修改切片里的数据(如 hand1[0] = 'X'),原始数组 cards 里的数据也会变!
2. 动态长度
注意类型写法的区别:
[8]u8:长度为 8 的数组(编译期确定)。[]u8:未知长度的切片(运行期确定)。
我们在编写通用函数(如字符串处理、缓冲区处理)时,几乎总是使用切片 []T 作为参数。
3. 安全性
虽然切片底层是指针,但 Zig 会对切片访问进行边界检查。如果你试图访问 hand1[10],程序在运行时会安全地 Panic,而不是像 C 语言那样读取非法内存。
后续预告:我们已经学会了切片。但是切片和“多项指针” ([*]T) 之间有什么关系?它们看起来很像,但安全性完全不同。下一篇我们将深入探讨切片的更多用法。