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(指向字节序列的常量切片)。 这种“揭开盖子看本质”的设计,虽然初学时稍显繁琐,但让你对内存有了绝对的控制权。

后续预告:我们已经学习了切片。现在,我们要把“切片”和“多项指针”结合起来。如果我有一个指针,我知道它后面跟着好几个元素,我怎么把它变成好用的切片呢?下一篇我们将学习“指针到切片的转换”。