Repository navigation
Struct Method Binding
上一章的 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.
方法内部,字段名可以直接用,不需要 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.
每个实例调用方法时,看到的是自己的字段。两个实例同时存在,互不影响:
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.
如果方法的参数和某个字段同名,参数会遮蔽字段:
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 关键字,就变成静态方法:它不能访问实例字段,只作用于自己的参数和全局变量。
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.
静态方法适合无状态工具:不依赖实例数据、纯计算的函数。像 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.
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().
调用 p.levelUp() 时,HimDSL 的处理顺序是:
When you call p.levelUp(), HimDSL resolves in this order:
-
如果
p是结构体实例,在它的类型里找同名方法。 -
找到则调用,参数个数必须匹配。
-
找不到会报错,不会尝试去调用全局同名函数。
-
If
pis a struct instance, look for a method of that name in its type. -
If found, call it; the argument count must match.
-
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.
-
结构体内可以定义方法:
返回类型 名称(参数) { ... }。 -
方法内直接访问字段,不用
this.或self.。 -
每个实例独立,方法看到的是自己的字段。
-
参数名会遮蔽同名字段,避免重名。
-
static方法不能访问实例字段,用类型名调用。 -
结构体方法与全局函数分开,互不冲突。
-
与概念绑定的逻辑写成方法,通用逻辑写成全局函数。
-
Structs may define methods:
returnType name(params) { ... }. -
Access fields directly; no
this.orself.. -
Each instance is independent.
-
Parameters shadow same-named fields; avoid collisions.
-
staticmethods cannot access instance fields; call via type name. -
Struct methods and global functions are separate namespaces.
-
Concept-bound logic → methods; general logic → global functions.
下一章 [enum 用户枚举 / Enums] 介绍如何定义一组有限的取值,例如状态、颜色、方向。
Next, [enum 用户枚举 / Enums] shows defining a finite set of values: states, colors, directions.
- 首页 / Home
- 安装与快速开始 / Installation & Quick Start
- 第一个脚本 / Your First Script
- 命令速查 / Command Reference
- 自动执行脚本 / Auto-run Scripts
- 定义函数 / Defining Functions
- 函数重载 / Function Overloading
- Lambda 表达式 / Lambda Expressions
- 函数式接口 / Functional Interfaces
- 选择器总览 / Selectors Overview
- 选择器过滤器 / Selector Filters
- EntityRef 引用 / EntityRef
- 占位符参考 / Placeholders Reference
- PlaceholderAPI 桥接 / PAPI Bridge
- 创建 GUI / Creating a GUI
- GUISlot 槽位 / GUISlot
- 点击回调 / Click Callbacks
- onClose 回调 / onClose Callback
- GUI 刷新 / GUI Refresh
- 数学函数 / Math Built-ins
- 高精度计算 / BigDecimal Built-ins
- 字符串函数 / String Built-ins
- 集合函数 / Collection Built-ins
- 文件 I/O / File I/O
- 命令与 sleep / Commands & sleep