#运算符取table的长度,不论在lua5.1,还是在lua5.3都不能保证结果的正确性,在某些场景下,我们可以使用select来替代#得到正确的结果。
测试代码1
local function test()
return 1, nil, 3, 4, nil, 6, nil, nil, nil, 10, nil, 12, nil
end
local ret = {test()}
print(string.format("ret的长度为: %d", #ret))
输出
root@localhost:~/work/test/lua lua sel1.lua
ret的长度为: 6
测试代码2
local function test()
return 1, nil, 3, 4, nil, 6, nil, nil, nil, 10, nil, 12, nil, 14
end
local ret = {test()}
print(string.format("ret的长度为: %d", #ret))
输出
root@localhost:~/work/test/lua lua sel1.lua
ret的长度为: 14
测试代码3
local function test()
return 1, nil, 3, 4, nil, 6, nil, nil, nil
end
local ret = {test()}
print(string.format("ret的长度为: %d", #ret))
输出
root@localhost:~/work/test/lua lua sel1.lua
ret的长度为: 6
测试代码4
local function test()
return 1, nil, 3, 4, nil, 6, nil, nil, nil, 10
end
local ret = {test()}
print(string.format("ret的长度为: %d", #ret))
输出
root@localhost:~/work/test/lua lua sel1.lua
ret的长度为: 10
测试代码5
local function test()
return 1, nil, 3, 4, nil, 6, nil, nil, nil, nil
end
local ret = {test()}
print(string.format("ret的长度为: %d", #ret))
输出
root@localhost:~/work/test/lua lua sel1.lua
ret的长度为: 1
以上测试结果在 lua5.1 和 lua5.3 环境均相同,官方手册也有说明,#table 必须是连续序列才能保证返回值的正确性。以前看过 # 取 table 长度在 lua 源码中的实现,还留有一点印象,再加上这个测试结果可以分析下 #table 的计算流程:用二分查找查找到尽可能大的值。流程图如下:
select (index, ···)
如果 index 是个数字, 那么返回参数中第 index 个之后的部分; 负的数字会从后向前索引(-1 指最后一个参数)。 否则,index 必须是字符串 “#”, 此时 select 返回参数的个数。 <
>
场景1:获取返回值数量
local function test()
return 1, nil, 3, 4, nil, 6, nil, nil, nil, 10, nil, 12, nil
end
print(select("#", test()))
正确返回了实际个数
root@localhost:~/work/test/lua lua sel2.lua
13
场景2:获取第n个返回值以后的数据
local function test()
return 1, nil, 3, 4, nil, 6, nil, nil, nil, 10, nil, 11, nil
end
print(select("#", test()))
print(select(4, test()))
输出了从第4个索引开始的所有返回值,包括nil
root@localhost:~/work/test/lua lua sel2.lua
13
4 nil 6 nil nil nil 10 nil 11 nil
场景3:获取变长参数的长度
function test(str, ...)
print(select("#", ...))
end
test(1, 1)
test(1, 1, 2)
test(1, 1, 2, 3)
test(1, nil, 2, 3)
test(1, nil, 2, nil)
test(1, nil, nil, nil)
都输出了正确的参数个数
root@localhost:~/work/test/lua lua select.lua
1
2
3
3
3
3
我们可以基于场景3 重写一下 print 方法:
function my_print(str, ...)
local len = select("#", ...)
str = len == 0 and str or string.format(str, ...)
print(str)
end
my_print("no val")
my_print("val1: %s, val2: %s, val3: %s", 1, 2, 3)
my_print("val1: %s, val2: %s, val3: %s", nil, 2, 3)
my_print("val1: %s, val2: %s, val3: %s", nil, 2, nil)
my_print("val1: %s, val2: %s, val3: %s", nil, nil, nil)
以下为 lua5.3 环境下的输出,lua5.1 还不支持 format 参数中非 string 类型自动转换为 string 类型,需要自己保证 format 的格式正确
root@localhost:~/work/test/lua lua53 select.lua
1
2
3
3
3
3
no val
val1: 1, val2: 2, val3: 3
val1: nil, val2: 2, val3: 3
val1: nil, val2: 2, val3: nil
val1: nil, val2: nil, val3: nil
select具体实现我没有去深入了解,可能会有性能方面的损耗,所以如果是在调度频繁的逻辑中使用的话需要慎重一点,可以看下源码实现或者自己测试一下。