转载

Lua学习笔记(5)table的长度

[TOC]

# 版本 lua-5.1.5

疑问

lua获取table长度的接口有很多:

  • table.getn()
  • table.maxn()
  • ’#’操作符

推荐一篇文章: 浅析Lua中table的遍历

但是也会出现神奇的情况,请看下面一小段代码:

local t = {111, x = 222, nil, 333, [10] = 555, {}, nil, nil} print(table.getn(t))    -- 4 print(#t)               -- 4 print(table.maxn(t))    -- 10 --local t = {111, nil, x = 222, nil, 333, [10] = 555, {}, nil, nil} --local t = {111, x = 222, nil, 333, [10] = 555, nil, {}, nil, nil} 

如果随机在t中插入nil,结果会让你大吃一惊,丈二和尚摸不着头脑。比如:

  • 在111后面插入一个nil,结果是: 1, 1, 10
  • 在555后面插入一个nil,结果却是:5, 5, 10

这是为什么呢?有没有规律呢?

table的#操作符

这里原因我先简单介绍一下,想弄更清楚的请自己看完之后看源代码,算是抛砖引玉^_^!上面的结果千奇百怪主要是和#操作符有关,#操作符最终获取table长度的函数是(文件ltable.c中):

/* ** Try to find a boundary in table `t'. A `boundary' is an integer index ** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil). */ int luaH_getn (Table *t) {   unsigned int j = t->sizearray;   if (j > 0 && ttisnil(&t->array[j - 1])) {     /* there is a boundary in the array part: (binary) search for it */     unsigned int i = 0;     while (j - i > 1) {       unsigned int m = (i+j)/2;       if (ttisnil(&t->array[m - 1])) j = m;       else i = m;     }     return i;   }   /* else must find a boundary in hash part */   else if (t->node == dummynode)  /* hash part is empty? */     return j;  /* that is easy... */   else return unbound_search(t, j); } 

只看很小的一部分:

while (j - i > 1) {   unsigned int m = (i+j)/2;   if (ttisnil(&t->array[m - 1])) j = m;   else i = m; } return i; 

unbound_search函数中也有一部分逻辑和上面代码一样

知道二分查找的人花一点时间应该很快能够理解这段代码的意思,从数组[i-j]中二分查找的方式遍历,如果不为ni的话,相当于长度增加了(i=m);如果为nil的话,相当于长度减少了,也举个简单的例子:

local t = {1, 2, nil} print("#t1 = ", #t1) --[[ i=0, j=3 (1)m=1, array[m-1]=1不为nil, i=m=1 (2)m=2, array[m-1]=2不为nil, i=m=2 (3)j-i <=1循环结束,return i,结果为2 ]]  local t = {1, nil, 2, nil} print("#t1 = ", #t1) --[[ i=0, j=4 (1)m=2, array[m-1]=nil, j=m=2 (2)m=1, array[m-1]=1不为nil, i=m=1 (3)j-i <=1循环结束,return i,结果为1 ]] 

可以看到,ni的位置不确定性,让返回的长度变幻莫测了,这也就是为什么上面看到的结果千奇百怪了 ^_^

总结

  • lua的’#’操作符获取的长度在table中间有nil的情况很不稳定
  • 虽然看到代码还是有一定规律可循的,但我个人认为也不应该把这个规律用来计算这种table的长度
  • 再回来看luaH_getn函数开头的一小段代码解释说明,会更容易理解一点:’Try to find a boundary in table t'. A boundary’ is an integer index such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil).’

拓展

## unpack({…}) 我们会经常用到这种方式,比如传进来的参数是不定长的’…‘,然后用table的操作符’{}’括起来,变成一个table,然后使用unpack解包。恰好,前两天有个同事跟我们分享了这个使用方式的漏洞。一开始有点吃惊,看完上面的一些分析只会,也就能够明晰许多了。下面是同事的分享。 ### 问题代码

function fnUnPack( ... )  local p = {...};  return unpack(p); end  print(fnUnPack(1, 2, 3)); print(fnUnPack(1, 2, 3, nil ,4)); print(fnUnPack(1, 2, 3, nil ,4, nil)); --结果 1 2 3 1 2 3 nil 4 1 2 3 

解决办法

  • (1)使用table的pack和unpack

示例代码:

function fnUnPack1( ... )  local p = table.pack(...);   return table.unpack(p, 1, p.n); end 

版本lua-5.2及以上

  • (2)使用select

示例代码:

function fnUnPack2( ... )  local p = {...}  local n = select('#', ...)  return unpack(p, 1, n); end 

##版本5.2及以上

  • lua-5.2以上已经没有table.getn和table.maxn接口了
原文  http://pkxpp.github.io/2016/06/08/lua学习笔记-5-table的长度/
正文到此结束
Loading...