Ziglings 笔记 48: 更多的方法 (Methods II)

像说话一样写代码

在 Ziglings 的第 48 个练习中,我们继续大象链表的探险。 这次不同的是,我们的大象变聪明了——它们拥有了一系列方法 (Methods)

在此之前,我们需要手动检查 tail 是否为 null。现在,我们可以直接“询问”大象对象。这展示了面向对象编程风格中“封装”的好处,尽管 Zig 并不是传统的 OOP 语言。

挑战:使用接口而非字段

我们需要重写 visitElephants 函数。 这一次,我们不应该直接访问 e.tail 字段,而是应该使用大象提供的新方法:hasTail()getTail() 来决定是否继续遍历。

解决方案

这是使用方法调用重构后的遍历逻辑:

const std = @import("std");

// ... Elephant 结构体定义包含新方法 ...
// pub fn hasTail(self: *Elephant) bool { ... }
// pub fn getTail(self: *Elephant) *Elephant { ... }

fn visitElephants(first_elephant: *Elephant) void {
    var e = first_elephant;

    while (true) {
        // 调用方法打印状态
        e.print();
        // 调用方法修改状态 (设置 visited = true)
        e.visit();

        // 核心逻辑:
        // 不再写 e.tail orelse break
        // 而是使用更具可读性的方法调用
        e = if (e.hasTail()) e.getTail() else break;
    }
}

核心知识点总结

1. 语义化编程

对比一下两种写法:

  • 写法 A: e = e.tail orelse break; (关注实现细节:它是 optional 吗?)
  • 写法 B: if (e.hasTail()) e = e.getTail() else break; (关注业务逻辑) 写法 B 虽然长一点,但它读起来就像英语句子。这种封装让代码的意图更加清晰。

2. 指针接收者 (self: *T)

visit 方法中:

pub fn visit(self: *Elephant) void {
    self.visited = true;
}

关键在于 self 的类型是 *Elephant。这允许方法修改调用它的实例。如果我们漏掉了 *,Zig 会传递结构体的副本,原来的大象永远不会被标记为“已访问”。

3. Enum 也有方法?

练习底部的注释提到了一个有趣的冷知识:Zig 的枚举 (Enum) 也可以定义方法! 这意味着你可以写这样的代码:

const Color = enum { red, green };
// 像结构体一样定义 fn
pub fn isHot(self: Color) bool { return self == .red; }

// 调用
const c = Color.red;
if (c.isHot()) { ... }

这证明了 Zig 的类型系统是多么的一致和灵活。


后续预告:我们已经通过了 Quiz 6(其实这个练习就是 Quiz 6 的前奏)。接下来的练习将是一个真正的“期中考试”——Quiz 6,它将综合考察结构体、方法和指针的所有知识。