Ziglings 笔记 47: 结构体的方法 (Methods)

它是方法,也是函数

在 Ziglings 的第 47 个练习中,我们迎来了一个重大的语法特性:方法 (Methods)

如果你习惯了 Java 或 C++ 的“类 (Class)”,你可能会觉得 Zig 的 struct 只是数据的容器。但通过这个练习,你会发现 Zig 的结构体其实也能包含行为。

挑战:外星人入侵

我们需要模拟一场战斗:

  1. Alien 结构体有一个“孵化”函数 hatch
  2. HeatRay(热射线)结构体有一个“攻击”函数 zap
  3. 我们需要用热射线遍历并消灭所有的外星人。

解决方案

这是实现攻击逻辑的代码:

const std = @import("std");

const Alien = struct {
    health: u8,

    // 静态函数风格:
    // 第一个参数不是 Alien 或 *Alien,所以只能用 Alien.hatch(...) 调用
    pub fn hatch(strength: u8) Alien {
        return Alien{
            .health = strength * 5,
        };
    }
};

const HeatRay = struct {
    damage: u8,

    // 方法风格:
    // 第一个参数是 self: HeatRay。
    // 这意味着我们可以用 dot syntax: instance.zap(...)
    pub fn zap(self: HeatRay, alien: *Alien) void {
        alien.health -= if (self.damage >= alien.health) alien.health else self.damage;
    }
};

pub fn main() void {
    var aliens = [_]Alien{
        Alien.hatch(2),
        Alien.hatch(1),
        // ...
    };

    var aliens_alive = aliens.len;
    const heat_ray = HeatRay{ .damage = 7 };

    while (aliens_alive > 0) {
        aliens_alive = 0;

        // 遍历所有外星人(获取指针)
        for (&aliens) |*alien| {
            
            // 核心代码:调用方法
            // 这行代码等价于:HeatRay.zap(heat_ray, alien)
            heat_ray.zap(alien);

            if (alien.health > 0) aliens_alive += 1;
        }
        std.debug.print("{} aliens. ", .{aliens_alive});
    }
    std.debug.print("Earth is saved!\n", .{});
}

核心知识点总结

1. 结构体即命名空间

在 Zig 中,struct 既是类型定义,也是命名空间。 像 Alien.hatch 这样的函数,就像是工厂方法。因为它不依赖于具体的实例,所以我们通过类型名直接调用它。

2. 语法糖:点号调用

当函数的第一个参数类型与结构体匹配时,Zig 允许我们使用 instance.method() 的写法。

  • 定义:fn zap(self: HeatRay, ...)
  • 调用:heat_ray.zap(...) 编译器会自动把 heat_ray 作为第一个参数 self 传进去。这让代码读起来非常自然,就像在操作一个对象一样。

3. self 只是个名字

不像 Python,Zig 没有把 self 设为关键字。你完全可以写成 fn zap(weapon: HeatRay, ...),然后用 weapon.damage。但为了社区规范和可读性,建议始终使用 self


后续预告:我们刚才的方法是不可变的(传入的是 HeatRay 值)。如果方法需要修改结构体自身的状态怎么办?下一篇我们将学习如何传递指针给方法(Mutable Methods)。