Ziglings 笔记 53: 字符串切片与 Const 安全性
一切归我所有
在 Ziglings 的第 53 个练习中,我们不仅重温了经典的 “Zero Wing” 游戏台词,还学到了关于字符串切片最重要的一条规则。
在其他语言中,切片可能只是切片。但在 Zig 中,可变性 (Mutability) 是类型系统的一部分。
挑战:重组句子
我们需要从字符串 scrambled 中提取片段,拼凑出 “All your base are belong to us” 和 “For great justice”。
难点不在于计算索引,而在于类型声明。
如果你尝试这样写:
// ❌ 错误:类型不匹配
const base1: []u8 = scrambled[15..23];
Zig 编译器会无情地拒绝你。
解决方案
正确的写法是显式加上 const:
const std = @import("std");
pub fn main() void {
// 字符串字面量存储在只读内存区
const scrambled = "great base for all your justice are belong to us";
// 1. 提取 "All your base are belong to us"
// 必须使用 []const u8,因为我们不能修改字符串字面量
const base1: []const u8 = scrambled[15..23]; // "all your"
const base2: []const u8 = scrambled[6..10]; // "base"
const base3: []const u8 = scrambled[32..]; // "are belong to us"
printPhrase(base1, base2, base3);
// 2. 提取 "For great justice"
const justice1: []const u8 = scrambled[11..14]; // "for"
const justice2: []const u8 = scrambled[0..5]; // "great"
const justice3: []const u8 = scrambled[24..31]; // "justice"
printPhrase(justice1, justice2, justice3);
std.debug.print("\n", .{});
}
// 函数参数也必须声明为 []const u8,以接受这些切片
fn printPhrase(part1: []const u8, part2: []const u8, part3: []const u8) void {
std.debug.print("'{s} {s} {s}.' ", .{ part1, part2, part3 });
}
核心知识点总结
1. 字符串字面量的内存位置
当我们写 const s = "hello" 时,这个 “hello” 是被硬编码在编译后的二进制文件中的。操作系统在加载程序时,会把它放在标为只读的内存页中。
2. 为什么必须是 []const?
如果 Zig 允许你创建 []u8 指向这个字符串,那么你就可以写 slice[0] = 'H'。
但这会导致程序在运行时因为尝试写入只读内存而崩溃(Segmentation Fault)。
Zig 在编译期通过类型检查拦截了这种错误:你不能获取只读数据的可写视图。
3. Zig 字符串的本质
在 Zig 中,没有专门的 string 类型。
- 字符串字面量是
*const [N:0]u8(指向以 null 结尾的字节数组的常量指针)。 - 字符串切片是
[]const u8(指向字节序列的常量切片)。 这种“揭开盖子看本质”的设计,虽然初学时稍显繁琐,但让你对内存有了绝对的控制权。
后续预告:我们已经学习了切片。现在,我们要把“切片”和“多项指针”结合起来。如果我有一个指针,我知道它后面跟着好几个元素,我怎么把它变成好用的切片呢?下一篇我们将学习“指针到切片的转换”。