Ziglings 笔记 101: 并行遍历与数据导向设计 (SoA)

像机器一样思考

在 Ziglings 的第 101 个练习中,我们不仅掌握了 for 循环的完全体形态,还接触到了高性能系统编程的核心哲学——数据导向设计 (Data-Oriented Design)

挑战:分离的属性

我们不再使用单一的 Character 结构体数组,而是将角色的属性拆分到了三个独立的数组中:

  • roles: 角色职业
  • gold: 金币数量
  • experience: 经验值

我们需要同时遍历这三个数组,并打印出一个从 1 开始的列表。

解决方案

Zig 的 for 循环支持并行遍历多个对象,并且支持使用 1.. 语法自动生成索引。

const std = @import("std");

const Role = enum {
    wizard, thief, bard, warrior,
};

pub fn main() void {
    const roles = [4]Role{ .wizard, .bard, .bard, .warrior };
    const gold = [4]u16{ 25, 11, 5, 7392 };
    const experience = [4]u8{ 40, 17, 55, 21 };

    // 核心语法:
    // 1. roles, gold, experience: 三个数组并行遍历 (Zip)
    // 2. 1.. : 生成从 1 开始的无限序列 (1, 2, 3, 4...)
    // 3. 循环会自动在最短的那个序列结束时停止 (也就是数组长度 4)
    for (roles, gold, experience, 1..) |c, g, e, i| {
        
        const role_name = switch (c) {
            .wizard => "Wizard",
            .thief => "Thief",
            .bard => "Bard",
            .warrior => "Warrior",
        };

        std.debug.print("{d}. {s} (Gold: {d}, XP: {d})\n", .{
            i, // 自动生成的索引
            role_name,
            g,
            e,
        });
    }
}

核心知识点总结

1. 索引范围语法 (start..)

这是 Zig 中最舒服的语法糖之一。

  • 0..:标准的索引生成。
  • 1..:用于生成行号等从 1 开始的场景。
  • 500..:你可以从任何数字开始。 这种语法只能配合 for 循环使用,不能单独作为一个值存在。

2. AoS vs SoA

  • AoS (Array of Structs): 面向对象编程 (OOP) 的默认思维。把数据按“逻辑对象”分组。
    • 比喻:把玻璃珠、勺子、羽毛按组放在一个个小袋子里。
  • SoA (Struct of Arrays): 数据导向设计 (DOD) 的思维。把数据按“类型”分组。
    • 比喻:把所有玻璃珠放一个袋子,所有勺子放一个袋子。
    • 优势:如果你现在的任务是“捡起所有勺子”,SoA 让你一次就能拿完,而 AoS 需要你打开每一个小袋子去翻找。

3. 为什么 Zig 适合 SoA?

Zig 强大的 Multi-object for loops 让处理 SoA 结构变得像处理 AoS 一样简单。 在 C++ 中,维护三个平行的 vector 并保持它们同步是件麻烦事,但在 Zig 中,for (a, b, c) 让这一切变得自然且安全(有长度检查)。


后续预告:我们已经彻底掌握了控制流。接下来的练习将进入一个新的篇章:测试 (Testing)。Zig 将测试作为语言的一等公民,你可以直接在源码文件中编写测试用例。下一篇见!