前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >编程小知识之 Lua 长度运算符(#)

编程小知识之 Lua 长度运算符(#)

作者头像
用户2615200
发布2019-12-25 16:31:57
1.9K0
发布2019-12-25 16:31:57
举报

本文讲解了 Lua 中长度运算符(#)的一些知识 (注: 以下讨论基于 Lua 5.3.5 版本)

基础

Lua 中的长度运算符(#)可以用于获取 table 的"长度",举个简单的例子:

代码语言:javascript
复制
local t = { 1, 1, 1 }
print(#t) -- 3

但其实对于 table 而言,长度运算符并不等同于获取 table 的"长度",更准确一些的说法应该是获取 table 序列部分的长度,而所谓序列,是指索引为 1 至 n 的集合(中间不能有空元素),以上面的代码为例,表(table) t 就是一个序列, 索引为 1 至 3,所以表(table) t 的长度即为 3.

而对于下面的 表(table) t:

代码语言:javascript
复制
local t = { 1, 1, 1, nil }

虽然表(table) t 有 4 个元素(索引为 1 至 4),但是索引 4 为空元素(nil),所以表(table) t 的序列部分索引是 1 至 3,所以表(table) t 的长度仍为 3:

代码语言:javascript
复制
local t = { 1, 1, 1, nil }
print(#t) -- 3

在实际开发中,也并不建议在用作序列的 table 中插入空元素(nil),所以一般来讲,能够在用作序列的 table 上正确使用长度运算符(#),并且了解长度运算符(#)的局限性(只能正确作用于序列上)就足够了.


以下内容涉及实现细节,讨论的示例也并不常见,仅想初步了解的朋友可以跳过阅读,否则容易引起混淆

进阶

接着上面的例子,我们再来看下这段代码:

代码语言:javascript
复制
local t = { 1, 1, nil, 1 }
print(#t) -- ?

按照之前的理解,似乎输出应为 2(因为表(table) t 的序列部分索引为 1 至 2),但实际上,程序的输出为 4:

代码语言:javascript
复制
local t = { 1, 1, nil, 1 }
print(#t) -- 4

原因在于 Lua 的相关实现中,长度是从最大的数组索引处开始查找的,如果发现该处的元素不为空(nil),就直接向后查询.

在上面的例子中, Lua 首先检查 t[4](t 的最大数组索引为 4),发现不是空元素,于是直接向后查询,发现不存在 t[5] 元素,于是便返回了 4(作为 table 的序列长度,下同).

我们接着来,考虑下面代码:

代码语言:javascript
复制
local t = { 1, 1, nil, 1, 1, nil }
print(#t) -- ?

按照之前的讲解,现在表(table) t 的最大数组索引处(t[6])为空元素,于是我们应该直接向前查找 t[5],然后发现 t[5] 并不是空元素,于是返回 5.

但实际上,程序的输出为 2:

代码语言:javascript
复制
local t = { 1, 1, nil, 1, 1, nil }
print(#t) -- 2

原因在于当 Lua 发现 table 最大数组索引处的元素为空时,是按二分法的方式向前查找的,当发现 t[6] 为空元素之后, Lua 向前查找的元素不是 t[5],而是 t[3],接着发现 t[3] 是空元素,于是从 t[3] 开始继续向前二分查找,最后返回了 2.

接着我们可以来做些练习了:

代码语言:javascript
复制
local t = { 1, nil, nil, nil, nil, nil, nil, nil }
print(#t) -- ?

按照上面的解释,我们很容易知道输出应为 1:

代码语言:javascript
复制
local t = { 1, nil, nil, nil, nil, nil, nil, nil }
print(#t) -- 1

接着我们进行赋值操作:

代码语言:javascript
复制
local t = { 1, nil, nil, nil, nil, nil, nil, nil }
print(#t) -- 1

t[8] = 1
print(#t) -- ?

这时 table 的最大数组索引处(t[8])不为空元素,按照先前的解释,输出会变成 8:

代码语言:javascript
复制
local t = { 1, nil, nil, nil, nil, nil, nil, nil }
print(#t) -- 1

t[8] = 1
print(#t) -- 8

我们再进行一次赋值操作:

代码语言:javascript
复制
local t = { 1, nil, nil, nil, nil, nil, nil, nil }
print(#t) -- 1

t[8] = 1
print(#t) -- 8

t[9] = 1
print(#t) -- ?

这个时候输出为多少呢?你也许会猜测是 9,但实际上输出为 1 !

代码语言:javascript
复制
local t = { 1, nil, nil, nil, nil, nil, nil, nil }
print(#t) -- 1

t[8] = 1
print(#t) -- 8

t[9] = 1
print(#t) -- 1

原因在于我们最后一次的赋值操作因为新建了索引(之前不存在索引 9),继而触发了 table 的 rehash 流程,在这个流程中, Lua 会根据 table 元素的分布重新调整数组的大小,使的最后的输出变为了 1(这里我们不展开 rehash 的流程细节,有兴趣深入的朋友可以看看 Lua 源码中的 rehash 函数).

高级

如果混合使用 table 中的 数组部分 和 hash部分,则长度运算符(#)的结果会更加复杂一些:

代码语言:javascript
复制
local t = { 1, 1, 1, 1, [5] = 1, [9] = 1 }
print(#t) -- ?

当 Lua 发现 table 的最大数组索引处不为空元素时,其会继续在 table 的 hash部分 寻找,继而导致上面的输出为 5:

代码语言:javascript
复制
local t = { 1, 1, 1, 1, [5] = 1, [9] = 1 }
print(#t) -- 5

另外, hash 部分的查找流程也是二分进行的,这也导致以下代码的输出为 10(而上面代码的输出为 5) :

代码语言:javascript
复制
local t = { 1, 1, 1, 1, [5] = 1, [10] = 1 }
print(#t) -- 10

最后一个例子有些隐晦,在此我们仅仅列出结果,有兴趣了解原因的朋友可以看看 Lua 源码中的 luaO_int2fb 和 luaO_fb2int 两个函数:

代码语言:javascript
复制
local t = { 1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 2 }
print(#t) -- 18

local t = { 1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 2 }
print(#t) -- 1
总结

了解 Lua 中长度运算符(#)的作用并不困难,但其中涉及的细节并不简单(有时候甚至有些隐晦),有兴趣深入的朋友可以从 Lua 源码中的 luaH_getn 函数开始探索.

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基础
  • 进阶
  • 高级
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档