Ziglings 笔记 103: 文本分词 (Tokenization)
切碎它!
在 Ziglings 的第 87 个练习中,我们开始接触标准库中的文本处理工具。 很多时候,我们需要处理像 “Hello, World!” 这样的字符串。如果我们想提取单词,就必须处理空格、逗号、感叹号等各种干扰。
手动写循环去判断每个字符是很繁琐且容易出错的。Zig 提供了 Tokenizer 来帮我们做这件脏活。
挑战:数单词
我们有一首雪莱的诗《奥斯曼狄斯》(Ozymandias)。 任务是统计这首诗里有多少个单词。 单词之间可能由空格、换行、分号、逗号或感叹号分隔。
解决方案
使用 std.mem.tokenizeAny,它可以指定多个分隔符:
const std = @import("std");
const print = std.debug.print;
pub fn main() !void {
const poem =
\\My name is Ozymandias, King of Kings;
\\Look on my Works, ye Mighty, and despair!
;
// 核心函数:tokenizeAny
// 参数 1: 元素类型 (u8)
// 参数 2: 输入字符串
// 参数 3: 分隔符集合 (空格, 分号, 逗号, 感叹号, 换行)
// 注意:tokenize 会自动跳过连续的分隔符(例如 ", " 不会产生空字符串)
var it = std.mem.tokenizeAny(u8, poem, " ;,!\n");
var cnt: usize = 0;
// 迭代器模式
// it.next() 返回 ?[]const u8
// 使用 while capture 语法,只要 next() 不返回 null,循环就继续
while (it.next()) |word| {
cnt += 1;
print("{s}\n", .{word});
}
print("This little poem has {d} words!\n", .{cnt});
}
核心知识点总结
1. tokenize vs split
这是 Zig 标准库中容易混淆的两个概念:
- Tokenize: 忽略空结果。适用于
"a, b"->["a", "b"]。 - Split: 保留空结果。适用于
"a,,b"->["a", "", "b"](比如 CSV 的空列)。
2. 迭代器 (Iterator)
tokenizeAny 不会一次性分配一个数组来存所有单词(那样需要内存分配)。相反,它返回一个轻量级的结构体(迭代器),每次调用 next() 时才去计算下一个单词的位置。
这是 零分配 (Zero Allocation) 的,非常高效。
3. 多行字符串
代码中使用了 \\ 语法。这是 Zig 的多行字符串字面量。它不需要在每行末尾加 \n,也不需要引号闭合,非常适合嵌入大段文本。
后续预告:我们已经学会了把字符串切开。接下来,我们要学习如何把字符串转换成数字(Parsing),以及如何处理更复杂的文件读写。下一篇我们将深入 std.fmt 和 std.fs。