Lua 长度运算符 (#) 与 nil 值
在阅读了这篇文章并进行了一些实验后,我正在尝试了解当一个表中包含 nil 值时,Lua 长度运算符的工作原理。
在我开始调查之前,我认为长度只是从索引 1 开始的连续非 nil 元素的数量:
print(#{nil}) -- 0
print(#{"o"}) -- 1
print(#{"o",nil}) -- 1
print(#{"o","o"}) -- 2
print(#{"o","o",nil}) -- 2
这看起来非常简单,对吧?
但当我在 nil 终止的表中偶然添加了一个元素后,我的头痛就开始了:
print(#{"o",nil,"o"})
我猜它应该打印 1,因为当第一个 nil 被找到时它会停止计数。或者,如果长度运算符足够贪婪,它应该打印 2,查找第一个 nil 之后的非 nil 元素。但上述代码打印 3。
所以我做了几个其他测试来看看会发生什么:
-- 结束之前有 nil
print(#{nil,"o"}) -- 2
print(#{nil,"o","o"}) -- 3
print(#{"o",nil,"o"}) -- 3
-- 几个 nil 元素
print(#{"o",nil,nil}) -- 1
print(#{nil,"o",nil}) -- 0
print(#{nil,nil,"o"}) -- 3
我应该提到的是,repl.it 目前使用的是相当旧的 Lua 5.1.5,但是如果您使用 Lua demo,它目前使用的是 Lua 5.3.5,您将获得相同的结果。
通过观察这些结果并查看这篇答案,我认为:
- 如果最后一个元素不是 nil,则长度运算符返回表的完整大小,包括任何 nil 条目
- 如果最后一个元素是 nil,则计算连续的非 nil 元素的数量,并在第一个 nil 处停止计数
这些假设正确吗?
当一个表包含一个或多个 nil 值时,我们能否预测一个 100% 定义良好的行为?
Lua 文档说明 只有当表是一个序列时,才定义了表的长度。这是否意味着长度运算符对非序列具有未定义的行为?
除了长度运算符,nil 值在表中会导致任何麻烦吗?
当一个表是一个序列(所有数字键从1开始且没有“nil”间隙),#被定义为这些元素的数量。
对于非序列表,它有点更加复杂。Lua 5.2似乎将结果未定义。对于5.1和5.3,运算的结果就是一个边界。
表中的边界是任何包含非“nil”值后跟“nil”的正索引,如果第一个元素是“nil”,则为0。#被定义为返回满足这些条件的任何值。
从另一个角度看,由于表包含“数组”部分和“映射”部分,Lua无法知道“映射”索引从哪里开始。例如,你可以创建一个包含1000个值的表,然后将它们的前999个值设为“nil”;这可能导致你得到一个“大小”为1000的表。然而,你也可以从一个空表开始,并设置第1000个元素,得到一个“大小”为0但在结构上等同于第一个表的表。然后,#的结果只是内部算法找到的第一个有效值。
我们可以预测一些行为,但它不是标准化的,因此您永远不应该依赖它。这个major version的Lua中这种行为很可能会发生变化。
如果你需要用nil值填充一个表,我建议使用一个独特的占位符值来包装表并替换空缺(例如 NIL={};if v==nil then t[k]=NIL,这很容易测试并且安全)。
尽管如此...
由于#结果受表如何定义的影响,您必须区分静态定义(常量)表和动态定义(静音)表。
静态表定义:
#{nil,nil,nil,nil,nil, 1} -- 6
#{3, 2, nil, 1} -- 4
#{nil,nil,nil, 1, 1,nil} -- 0
#{nil,nil, 1, 1, 1,nil} -- 5
#{nil, 1, 1, 1, 1,nil} -- 5
#{nil,nil,nil,nil, 1,nil} -- 0
#{nil,nil, 1,nil, 1,nil,nil} -- 5
#{nil,nil,nil, 1,nil,nil, 1,nil} -- 4
使用这种定义,只要最后一个值不为nil,您将获得等于最后一个值位置的长度。 如果最后一个值为nil,Lua会从尾部开始进行(非线性)搜索,直到找到第一个非nil值。
动态数据定义
local x={}; x[5]=1;print(#x) -- 0
local x={}; x[1]=1;x[2]=1;x[3]=1;x[5]=1;print(#x) -- 3
local x={}; x[1]=1;x[2]=1;x[4]=1;x[5]=1;print(#x) -- 5
#{[5]=1} -- 0
local x={nil,nil,nil,1};x[5]=1;print(#x) -- 0
一旦表被更改,运算符的工作方式就会相反(这包括使用[]的静态定义)。如果第一个元素是nil,#总是返回0,但如果不是,它会开始搜索(我没有深入研究,我猜你可以检查源代码),直到找到一个在非nil 值之前的nil值。
如前所述,依赖这种行为不是一个好主意,并且会导致许多问题。但是,如果您想制作一个混乱的不可维护的程序来干扰同事,那么这是一种可靠的方法。
- Lua 虚拟机加密load(string.dump(function)) 后执行失败问题如何解决
- 我想创建一个 Nginx 规则,禁止访问
- 如何将两个不同的lua文件合成一个 东西有点长 大佬请耐心看完 我是小白研究几天了都没搞定
- 如何在roblox studio中1:1导入真实世界的地形?
- 求解,lua_resume的第二次调用继续执行协程问题。
- 【上海普陀区】内向猫网络招募【Skynet游戏框架Lua后端程序员】
- SF爱好求教:如何用lua实现游戏内调用数据库函数实现账号密码注册?
- Lua实现网站后台开发
- LUA错误显式返回,社区常见的规约是怎么样的
- lua5.3下载库失败
- 请问如何实现文本框内容和某个网页搜索框内容连接,并把网页输出来的结果反馈到另外一个文本框上
- lua lanes多线程使用
- 一个kv数据库
- openresty 有没有比较轻量的 docker 镜像
- 想问一下,有大佬用过luacurl吗
- 在Lua执行过程中使用Load函数出现问题
- 为什么 neovim 里没有显示一些特殊字符?
- Lua比较两个表的值(不考虑键的顺序)
- 有个lua简单的项目,外包,有意者加微信 liuheng600456详谈,最好在成都
- 如何在 Visual Studio 2022 中运行 Lua 代码?

length运算符对于非序列的表(即中间含有nil元素的表)产生未定义行为。这意味着即使 Lua 实现始终以某种方式进行操作,您也不应该依赖该行为,因为它可能会在将来的 Lua 版本中或在不同的实现(如 LuaJIT)中更改。您可以在表中使用 nil。这没有问题,只需不要在可能存在 nil 的表上使用长度运算符来计算非 nil 值的长度。
您所链接的帖子包含有关实际算法的更多细节。它提到用二分查找算法计算元素。这不同于逐个计算元素 - 如果表中存在 nil,则根据它们的确切位置,二分查找算法可能将它们视为表的末尾或忽略它们。
总之,算法比您所认为的更难以预测,即使在任何给定情况下可以技术上预测会发生什么,您也不应该依赖于该行为,因为它可能会更改。