Ziglings 笔记 38: 结构体数组与 undefined 之谜
队伍集结
在 Ziglings 的第 38 个练习中,我们将结构体放入了数组中。这是构建复杂程序(比如游戏中的实体列表、通过 ID 查找记录等)的基础。
此外,这个练习还顺带揭示了 Zig 作为一个“安全”的系统语言,是如何处理未初始化内存的。
挑战:添加队员
我们有一个容量为 2 的数组 chars。第一个位置已经被 “Glorp the Wise” 占据了。我们需要在第二个位置(索引 1)添加 “Zump the Loud”。
解决方案
const std = @import("std");
const Role = enum {
wizard,
thief,
bard,
warrior,
};
const Character = struct {
role: Role,
gold: u32,
health: u8,
experience: u32,
};
pub fn main() void {
// 1. 声明数组但不初始化内容
// undefined 表示:“我有意留空,稍后赋值”
var chars: [2]Character = undefined;
// 初始化第一个角色
chars[0] = Character{
.role = Role.wizard,
.gold = 20,
.health = 100,
.experience = 10,
};
// 2. 初始化第二个角色 (Zump)
chars[1] = Character{
.role = Role.bard,
.gold = 10,
.health = 100,
.experience = 20,
};
// 遍历打印
for (chars, 0..) |c, num| {
std.debug.print("Character {} - G:{} H:{} XP:{}\n", .{
num + 1, c.gold, c.health, c.experience,
});
}
}
核心知识点总结
1. 结构体数组
var chars: [2]Character 在内存中是连续存放的。这与 Java 或 Python 的对象数组不同(后者通常是引用的数组)。在 Zig 中,这意味着极高的数据局部性和缓存效率。
2. undefined 的两面性
使用 undefined 是有风险的。如果你声明了它却忘了给 chars[1] 赋值,然后去读取它,会发生什么?
- 在 ReleaseFast 模式下:这是未定义行为 (UB)。程序可能打印乱码,也可能崩溃,或者被黑客利用。
- 在 Debug 模式下:Zig 会帮我们一把。
3. 神奇的 0xAA
为了帮开发者捉虫,Zig 在 Debug 模式下会将 undefined 的内存区域填充为字节 0xAA(二进制 10101010)。
- 如果你看到一个
u8变成了170。 - 或者一个
u32变成了2863311530(即0xAAAAAAAA)。 - 或者一个指针指向了地址
0xAAAAAAAA。
这就意味着:你正在访问未初始化的内存! 这种设计让隐蔽的内存错误变得显而易见。
后续预告:我们已经学会了定义数据。但是,C++ 或 Java 程序员可能会问:“方法在哪里?” 在 Zig 中,我们也可以在结构体内部定义函数。下一篇我们将学习 Pointers(指针),这是理解 Zig 方法调用的前提。