Struct Method Binding - HimxhMC/HimDSL GitHub Wiki

结构体方法绑定 / Struct Method Binding

结构体也能有方法 / Structs Can Have Methods

上一章的 PlayerData 只有字段。你需要在外面写函数去操作它:

The PlayerData from the previous chapter had only fields; you wrote external functions to use it:

void levelUp(PlayerData d) {
    d.level += 1;
}

更好的是把方法放进结构体,让它"自带行为":

Better to put the method inside the struct so it carries its own behavior:

struct PlayerData {
    string name;
    int level;
    double health;

    void levelUp() {
        level += 1;
    }
};

调用:

Call it:

PlayerData p;
p.name = "Steve";
p.level = 10;

p.levelUp();
runcmd("CONSOLE", "say " + p.level);    // 11

p.levelUp() 就像 p 自己的方法一样,简洁清晰。

p.levelUp() reads as p's own method — clean and clear.


方法怎么读写字段 / How Methods Read and Write Fields

方法内部,字段名可以直接用,不需要 this. 或 self. 前缀:

Inside a method, field names are directly usable — no this. or self. prefix:

struct Counter {
    int value;

    void increment() {
        value += 1;         // 直接访问字段
    }

    int get() {
        return value;       // 直接读取字段
    }
};

HimDSL 的处理方式很直观:方法执行前,把实例的字段"注入"到方法环境里;方法执行完,把环境里的值"写回"实例。

The mechanism is straightforward: before the method runs, the instance's fields are injected into the method's environment; after it runs, the environment's values are written back to the instance.

你不需要关心这个机制,但理解它有助于解释下面的行为。

You do not have to think about this, but understanding it explains the behavior below.


多个实例互不干扰 / Instances Are Isolated

每个实例调用方法时,看到的是自己的字段。两个实例同时存在,互不影响:

Each instance sees its own fields. Two instances coexist without interference:

struct Counter {
    int value;
    void increment() { value += 1; }
};

int main() {
    Counter a;
    a.value = 10;

    Counter b;
    b.value = 100;

    a.increment();      // a.value → 11
    b.increment();      // b.value → 101

    runcmd("CONSOLE", "say a=" + a.value + " b=" + b.value);
    // a=11 b=101
}

这就是"实例方法"的意义:方法的行为随实例而异。

This is the point of instance methods: behavior depends on the instance.


参数与字段同名 / Parameters with Field Names

如果方法的参数和某个字段同名,参数会遮蔽字段:

If a parameter shares a name with a field, the parameter shadows the field:

struct Player {
    int level;

    void setLevel(int level) {
        level = level;      // ✗ 两个 level 都是参数,字段没变
    }
};

HimDSL 的处理规则是:参数名会覆盖字段名,且同名的字段不会被写回。上面这段代码什么也不做。

HimDSL's rule: parameter names shadow field names, and a shadowed field is not written back. The code above does nothing.

避免给参数取和字段一样的名字。换个名字:

Avoid naming parameters after fields. Use a different name:

void setLevel(int newLevel) {
    level = newLevel;       // ✓ 字段 = 参数
}

静态方法 / Static Methods

方法前加 static 关键字,就变成静态方法:它不能访问实例字段,只作用于自己的参数和全局变量。

Prefix a method with static to make it a static method: it cannot access instance fields — only parameters and globals.

struct MathUtil {
    static int square(int x) {
        return x * x;
    }

    static int cube(int x) {
        return x * x * x;
    }
};

调用静态方法用类型名,不通过实例:

Call static methods through the type name, not an instance:

int a = MathUtil.square(5);     // 25
int b = MathUtil.cube(3);       // 27

如果你通过实例调用静态方法,会报错。反过来,通过类型名调用实例方法,也会报错。

Calling a static method on an instance errors, and vice versa.

什么时候用静态 / When to Use Static

静态方法适合无状态工具:不依赖实例数据、纯计算的函数。像 MathUtil.square 就是例子。

Static methods suit stateless utilities: functions that do not depend on instance data. MathUtil.square is a good example.

日常脚本里,静态方法的机会不多。一般直接写普通函数就够了。只有在"这个计算明显属于某个概念"时,才用结构体的静态方法归类。

Everyday scripts rarely need static methods. Ordinary functions usually suffice. Reach for a static method only when the computation clearly belongs to a concept.


一个综合例子 / A Comprehensive Example

struct Vec2 {
    double x;
    double y;

    double length() {
        return sqrt(x * x + y * y);
    }

    void scale(double factor) {
        x *= factor;
        y *= factor;
    }

    static Vec2 zero() {
        Vec2 v;
        v.x = 0.0;
        v.y = 0.0;
        return v;
    }
};

int main() {
    Vec2 v = Vec2.zero();
    v.x = 3.0;
    v.y = 4.0;

    runcmd("CONSOLE", "say 长度: " + v.length());    // 5.0

    v.scale(2.0);
    runcmd("CONSOLE", "say 缩放后: " + v.x + "," + v.y);    // 6.0, 8.0
}

length 和 scale 是实例方法,作用于具体的 v。zero 是静态方法,用于构造默认值,所以通过 Vec2.zero() 调用。

length and scale are instance methods acting on a specific v. zero is static and creates a default, so it is called via Vec2.zero().


方法调用与方法名字的解析 / Method Resolution

调用 p.levelUp() 时,HimDSL 的处理顺序是:

When you call p.levelUp(), HimDSL resolves in this order:

  1. 如果 p 是结构体实例,在它的类型里找同名方法。

  2. 找到则调用,参数个数必须匹配。

  3. 找不到会报错,不会尝试去调用全局同名函数。

  4. If p is a struct instance, look for a method of that name in its type.

  5. If found, call it; the argument count must match.

  6. If not found, error — no attempt to call a global function of the same name.

这意味着结构体方法与全局函数是分开的两个命名空间。你可以有一个全局 levelUp(PlayerData d),同时有一个方法 PlayerData.levelUp(),它们互不干扰。

This means struct methods and global functions live in separate namespaces. You can have a global levelUp(PlayerData d) and a method PlayerData.levelUp() without conflict.

选择哪一个? 与某个概念紧密相关的逻辑放进方法,通用工具放进全局函数。

Which to choose? Logic tightly bound to a concept → method; general utilities → global function.


小结 / Summary

  • 结构体内可以定义方法:返回类型 名称(参数) { ... }。

  • 方法内直接访问字段,不用 this. 或 self.。

  • 每个实例独立,方法看到的是自己的字段。

  • 参数名会遮蔽同名字段,避免重名。

  • static 方法不能访问实例字段,用类型名调用。

  • 结构体方法与全局函数分开,互不冲突。

  • 与概念绑定的逻辑写成方法,通用逻辑写成全局函数。

  • Structs may define methods: returnType name(params) { ... }.

  • Access fields directly; no this. or self..

  • Each instance is independent.

  • Parameters shadow same-named fields; avoid collisions.

  • static methods cannot access instance fields; call via type name.

  • Struct methods and global functions are separate namespaces.

  • Concept-bound logic → methods; general logic → global functions.


下一步 / Next Steps

下一章 [enum 用户枚举 / Enums] 介绍如何定义一组有限的取值,例如状态、颜色、方向。

Next, [enum 用户枚举 / Enums] shows defining a finite set of values: states, colors, directions.