点赞
用户11544408
用户11544408

我问了一个类似的问题

总结一下:由于Lua不支持在表中存储nil值,它将计算直到非nil元素之一。你需要使用ipairs手动计数。这里是参考文献

2019-07-15 09:40:10
用户4984564
用户4984564

在 Lua 中,数组和 nil 是一个复杂的话题。

当你使用 table.pack 时,它会计算参数的数量并将表的 n 值设置为该数量。但如果你使用如 {1, nil, 3, nil} 这样的表构造器,就不是这种情况了。

然而,# 运算符做的又是稍微不同的一件事。

根据手册

对于一个表,长度操作符返回的是一个表的一个边界。一个边界指的是任意一个在表 t 中,自然数索引为一个非 nil 的值,其后跟随一个 nil 值(或者自然数索引为 1 且它为 nil)的自然数索引。

这意味着,对于表

t = {1, nil, 3, nil}

它可以返回一个 1,因为 t[1]1t[2]nil,或是一个 3,因为 t[3]3t[4]nil

ipairs() 函数则又是做了完全不同的事情:它实际上从 1 开始计算索引,直到在表中遇到一个 nil,因此它总是统计到表中的第一个 _边界_。

如果你想让 # 运算符返回表的 n 值,在 Lua5.2 或更新的版本中,你可以这样做:

local t = {n=10}
setmetatable(t, {__len=function(self) return self.n end})
print(#t) -- 将输出 10,即使 t 没有 0 的数字索引

需要注意的是,在基于 Lua 5.1 的 LuaJIT 中,这种方法是无效的。

同样地,你也可以设置一个表 t__ipairs 元表方法,这样 ipairs(t) 就会从 1 开始计数,直到表的 n 值而不是第一个 nil 元素。


编辑:为什么 ipairs 对你的示例没有起到任何作用呢,这主要是由于我已经解释的其关于 ipairs 的特性,另外,由于表中的第一个键是 nil,它会马上假定表为空,因此什么也不做。


尽管这不是很相关,因为你不应该依赖这种特定实现的行为,但是下面是 PUC Lua 5.3 对表的 # 运算符的实现方法:

/*
** 尝试在表 't' 中找到一个边界。一个“边界”指的是一个整数索引,其为非 nil 值,而后一项为 nil 值 (如果 t[1] 是 nil 则为0)。
*/
lua_Unsigned 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 (isdummy(t))  /* hash part is empty? */
    return j;  /* that is easy... */
  else return unbound_search(t, j);
}

你可以看到,Lua 使用二分搜索来找到边界,这只有在你假定只有一个边界时才有意义;否则,它可能会随机跳过第一个,但突然间在表中添加一个值时就能找到它。

另外需要注意的是,表的数组部分不会每次添加一个值就增加大小。如果我没记错的话,它是每次总是翻倍的;所以你可以有一个四个元素的表,添加一个元素,Lua 会将数组大小增加到 8,再添加另外 4 个元素,它就会增加到 16,尽管表中只有 9 个元素。这是为了避免不必要的内存分配。

2019-07-15 11:36:30