【语法算法】
元方法 __len 的触发就这一行:
setmetatable(t, { __len = function(t) return #t.data end })
对带 __len 的表做 #t 操作时,Lua 调用 __len 而不是返回内置的表长度——自定义长度的全部机制就这一行。今天拆开。
一、默认、覆盖、联动三段
__len 的三段。默认:没有 __len 时 #t 返回表的边界——数组部分返回最后一个整数索引——哈希部分的长度不确定。覆盖:设置了 __len 后 #t 调用自定义函数返回自定义长度——对于封装了数据的表,内置的 #t 返回 0(因为表没有数组部分)——__len 让自定义容器也能正确响应 # 操作。联动:__len 和 __index 联动——__index 管"读取不存在的键",__len 管"长度是多少"——两者组合可以实现"自定义容器":表封装了内部数据但对外暴露统一的接口。三段连起来:__len 玩的是"把表变成自定义容器"——表的内部实现对外不可见,对外只暴露统一的操作接口。
graph TD
A["#t"] --> B{"t有__len?"}
B -->|是| C["调用__len(t)"]
C --> D["返回自定义长度"]
B -->|否| E["返回内置边界长度"]
二、自定义容器的正确写法
local Stack = {}
Stack.__index = Stack
function Stack.new()
return setmetatable({ items = {} }, Stack)
end
function Stack:push(v)
self.items[#self.items + 1] = v
end
function Stack:pop()
local v = table.remove(self.items)
return v
end
Stack.__len = function(s)
return #s.items
end
local stack = Stack.new()
stack:push("a")
stack:push("b")
stack:push("c")
print(#stack) -- 3: 走__len元方法
#stack 走 __len 元方法返回内部表的长度——没有 __len 时 #stack 返回 0——因为 stack 表本身没有数组部分——__len 让自定义容器也能正确响应 # 操作。
三、__len 的盲区和自定义容器的边界
__len 有一个盲区:只对 # 操作符生效——table.insert 和 table.remove 不走 __len——它们操作的是表的原始数组部分——自定义容器如果要支持 insert 和 remove,必须自己实现这两个方法。自定义容器的边界也要写清:__len 返回的值可以是任何数字——但调用方通常期望返回值是非负整数——返回负数或小数会导致调用方的逻辑出错——__len 的返回值就是容器的"大小承诺",承诺了就要兑现。最后一条纪律:__len 函数不能修改表——__len 应该是纯查询函数,不应该有副作用——修改了表就破坏了 # 操作符的语义,# 操作符在 Lua 里是只读操作。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
【游戏功能】 再抛一个老问题:奶量是固定好还是浮动好?固定回五十的术式,满血喝是五十、残血喝也是五十,账目清楚,可总差点意思…
【游戏功能】 前阵子一场"永动机事故"在群里传了半天:某个会喷火的精英怪被玩家围了一整晚,火喷了八个钟头没停——后台一看,它…
【游戏功能】 mode=mode==='red'?'blue':'red' ——先拆这一行。一个变量存着当前纹色,三元运算符…
【游戏功能】 上一版埋了个查了半宿的隐蔽报错:测试组报"玩家血条归零了人还站着",后台的死亡日志一条没有,复活接口也没被调过…
【游戏功能】 先抛一个坑:增益为什么只能自己挂?怒气、护体、神行,攻略里全在教你"什么时候开自己的招",可怪身上明晃晃挂着的…
【游戏功能】 昨天夜里一场线上事故把"反纹"顶上了台面:一个玩家把反纹挂在自己身上站桩挂机,怪的火球过来他没倒,反倒躺下的是…