专栏首页tkokof 的技术,小趣及杂念编程小知识之 Lua 长度运算符(#)

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

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

基础

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

local t = { 1, 1, 1 }
print(#t) -- 3

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

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

local t = { 1, 1, 1, nil }

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

local t = { 1, 1, 1, nil }
print(#t) -- 3

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


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

进阶

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

local t = { 1, 1, nil, 1 }
print(#t) -- ?

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

local t = { 1, 1, nil, 1 }
print(#t) -- 4

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

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

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

local t = { 1, 1, nil, 1, 1, nil }
print(#t) -- ?

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

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

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.

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

local t = { 1, nil, nil, nil, nil, nil, nil, nil }
print(#t) -- ?

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

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

接着我们进行赋值操作:

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

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

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

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

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

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

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 !

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部分,则长度运算符(#)的结果会更加复杂一些:

local t = { 1, 1, 1, 1, [5] = 1, [9] = 1 }
print(#t) -- ?

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

local t = { 1, 1, 1, 1, [5] = 1, [9] = 1 }
print(#t) -- 5

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

local t = { 1, 1, 1, 1, [5] = 1, [10] = 1 }
print(#t) -- 10

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

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 函数开始探索.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 问题小记之 使用 nil 索引 Lua table

    使用 Lua 已经不少时间了,遇到 “table index is nil” 的错误也很多次了,久而久之自己便形成了 Lua table 索引不能为 nil 的...

    用户2615200
  • Sweet Snippet系列 之 元素删除

      平时代码总会遇到一些关于集合的操作,例如添加,排序等等,都可算作稀松平常,但是集合涉及的删除操作却一直有个大坑,我自己便跳进去过好几回,在此简单一记,以自警...

    用户2615200
  • 编程小知识之 Dispose 模式

    之前对 C# 中的 Dispose 模式只有些模糊印象,近来又了解了一些相关知识,在此简单做些记录~

    用户2615200
  • 使用sniff 轻松抓取kubernetes pod的数据报文

    之前我们在k8s上进行pod级别的抓包,一般要好几步才能实现,参见这里:https://blog.51cto.com/lee90/2432209

    二狗不要跑
  • Go语言 nil 的作用

    最近在油管上面看了一个视频:Understanding nil,挺有意思,这篇文章就对视频做一个归纳总结,代码示例都是来自于视频。

    我的小碗汤
  • 理解Go语言的nil

    最近在油管上面看了一个视频:Understanding nil,挺有意思,这篇文章就对视频做一个归纳总结,代码示例都是来自于视频。

    李海彬
  • iOS实现视频和图片的上传

    这里有事先创建两个可变数组uploadArray, uploadedArray, 一个存放准要上传的内容, 一个存放上传完的内容

    周希
  • iOS WKWebview的iOS 11以下崩溃问题

    以上崩溃问题,经发现是没有removeObserver或者delegate没有设置为nil产生

    freesan44
  • 【Go 语言社区】Golang中interface判断nil问题

    在示例中,我们定义一个interface名为Stringer,同时定义一个符合其定义的Binary类型: type Stringer interface { ...

    李海彬
  • 算法:链表之环形链表

    该类题目的核心点在于如何判断是环形链表,核心思想是:两个人在环上跑步,跑的快的早晚会追上跑的慢的。

    灰子学技术

扫码关注云+社区

领取腾讯云代金券