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,它将综合考察结构体、方法和指针的所有知识。