【语法算法】
元方法 __unm 的触发就这一行:
setmetatable(t, { __unm = function(t) return setmetatable({ val = -t.val }, getmetatable(t)) end })
对带 __unm 的表做一元负号操作 -t 时,Lua 调用 __unm——一元负号的自定义全部机制就这一行。可这一行的前后各有一个细节:前面是"一元运算只有一个操作数",后面是"返回值可以是任何类型"。今天拆开。
一、注册、触发、返回三段
__unm 的三段。注册:元表的 __unm 字段赋一个函数——这个函数只接收一个参数——就是触发负号操作的那个表自身。触发:-t 触发 __unm(t)——一元运算只有一个操作数——不像 __add 有左右两个操作数——__unm 的语义就是"取反"。返回:__un 可以返回任何类型的值——返回同类型的表就是"取反后的新对象"——返回数字就是"取反后的数值"——返回什么由语义决定。三段连起来:__unm 玩的是"一元运算的自定义"——一元运算只有一个操作数,没有左右之分,语义最简单但实现最容易出错。
graph TD
A["-t"] --> B{"t有__unm?"}
B -->|是| C["调用__unm(t)"]
C --> D["返回取反结果"]
B -->|否| E["尝试转数字取负"]
E --> F["转不了就报错"]
二、一元负号的正确写法
local Vector = {}
Vector.__index = Vector
function Vector.new(x, y)
return setmetatable({ x = x, y = y }, Vector)
end
Vector.__unm = function(v)
return Vector.new(-v.x, -v.y)
end
local v1 = Vector.new(3, 4)
local v2 = -v1
print(v2.x, v2.y) -- -3, -4
__unm 返回一个新的 Vector 对象——不修改原始对象——一元运算的纯函数性就体现在"不修改输入,只返回输出"。
三、一元和二元的分界与纯函数的纪律
一元和二元的分界线就一条:操作数的个数——__unm 一个操作数、__add 两个操作数——Lua 通过元方法名区分一元和二元——不会混淆。纯函数的纪律是 __unm 的核心:不修改原始对象——返回新对象而不是修改旧对象——副作用为零——这样的 __unm 才是安全的。最后一条纪律:__unm 的返回值类型不必和输入相同——可以对一个表取负返回一个数字——但这样做的语义很奇怪——返回同类型是最佳实践,除非有充分的理由返回不同类型。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
【游戏功能】 数值组出过一次结算事故:斩残纹上线首日,满血的怪挨了最重的刀——按设计它该是最轻的那一刀。排查下来是加成算错了…
【游戏功能】 defQ=Math.min(30,defQ+6) ——先拆这一行。逆纹把重刀分期,每一拍记一笔挂账,但挂账不能…
【游戏功能】 上一版留了个只在你自己的房间里能复现的怪事:双生纹在怪倒下的那一帧偶尔整个消失——不跳、不报错、日志干净。翻了…
【游戏功能】 先抛一个坑:两只怪为什么只打你?它们的同伴就站在旁边,刀却一爪一爪全落你身上——仇恨锁死在玩家身上,怪和怪之间…
【游戏功能】 前阵子出过一场投诉事故:一个玩家连着七刀全是最小值,气得在世界频道喊"这纹出老千"。运营翻了账,七刀的点数真真…
【游戏功能】 P.hp=Math.max(5,P.hp-3) ——先拆这一行。反噬纹每秒要从血条里抽三点,但抽到见底会把人抽…