LUA TABLE 遍历数组的应用

本文深入探讨Lua中Table的4种遍历方法及其特点,包括paires、ipairs及利用table.maxn和'#'符号的遍历方式,并介绍了一个高效遍历Table的迭代器实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

当我在工作中使用lua进行开发时,发现在lua中有4种方式遍历一个table,当然,从本质上来说其实都一样,只是形式不同,这四种方式分别是:

复制代码
for key, value in pairs(tbtest) do  
    XXX  
end 
 
for key, value in ipairs(tbtest) do  
    XXX  
end 
 
for i=1, #(tbtest) do  
    XXX  
end 
 
for i=1, table.maxn(tbtest) do  
    XXX  
end 
复制代码

下面依次来讲讲四种遍历方式,首先来看for k,v in pairs(tbtest) do这种方式:

先看效果:

复制代码
tbtest = {  
    [1] = 1,  
    [2] = 2,  
    [3] = 3,  
    [4] = 4,  
} 
 
for key, value in pairs(tbtest) do  
    print(value)  
end 
复制代码

我认为输出应该是1,2,3,4,实际上的输出是1,2,4,3。我因为这个造成了一个bug,这是后话。

也就是说for k,v in pairs(tbtest) do 这样的遍历顺序并非是tbtest中table的排列顺序,而是根据tbtest中key的hash值排列的顺序来遍历的。 

当然,同时lua也提供了按照key的大小顺序来遍历的,注意,是大小顺序,仍然不是key定义的顺序,这种遍历方式就是for k,v in ipairs(tbtest) do。

for k,v in ipairs(tbtest) do 这样的循环必须要求tbtest中的key为顺序的,而且必须是从1开始,ipairs只会从1开始按连续的key顺序遍历到key不连续为止。

复制代码
tbtest = {  
    [1] = 1,  
    [2] = 2,  
    [3] = 3,  
    [5] = 5,  
} 
 
for k,v in ipairs(tbtest) do  
    print(v)  
end 
复制代码

只会打印1,2,3。而5则不会显示。

复制代码
local tbtest = {  
    [2] = 2,  
    [3] = 3,  
    [5] = 5,  
} 
 
for k,v in ipairs(tbtest) do  
    print(v)  
end 
复制代码

这样就一个都不会打印。

第三种遍历方式有一种神奇的符号'#',这个符号的作用是是获取table的长度,比如:

 

tbtest = {  
    [1] = 1,  
    [2] = 2,  
    [3] = 3,  
}  
print(#(tbtest)) 

打印的就是3

tbtest = {  
    [1] = 1,  
    [2] = 2,  
    [6] = 6,  
}  
print(#(tbtest)) 

这样打印的就是2,而且和table内的定义顺序没有关系,无论你是否先定义的key为6的值,‘#’都会查找key为1的值开始。

如果table的定义是这样的:

复制代码
tbtest = {  
    ["a"] = 1,  
    [2] = 2,  
    [3] = 3,  
} 
 
print(#(tbtest)) 
复制代码

那么打印的就是0了。因为‘#’没有找到key为1的值。同样:

tbtest = {  
    [“a”] = 1,  
    [“b”] = 2,  
    [“c”] = 3,  
}  
print(#(tbtest)) 

打印的也是0

所以,for i=1, #(tbtest) do这种遍历,只能遍历当tbtest中存在key为1的value时才会出现结果,而且是按照key从1开始依次递增1的顺序来遍历,找到一个递增不是1的时候就结束不再遍历,无论后面是否仍然是顺序的key,比如:

 

table.maxn获取的只针对整数的key,字符串的key是没办法获取到的,比如:

复制代码
tbtest = {  
    [1] = 1,  
    [2] = 2,  
    [3] = 3,  
}  
print(table.maxn(tbtest)) 
 
tbtest = {  
    [6] = 6,  
    [1] = 1,  
    [2] = 2,  
}  
print(table.maxn(tbtest)) 
复制代码

这样打印的就是3和6,而且和table内的定义顺序没有关系,无论你是否先定义的key为6的值,table.maxn都会获取整数型key中的最大值。

如果table的定义是这样的:

tbtest = {  
    ["a"] = 1,  
    [2] = 2,  
    [3] = 3,  
}  
print(table.maxn(tbtest)) 

那么打印的就是3了。如果table是:

复制代码
tbtest = {  
    [“a”] = 1,  
    [“b”] = 2,  
    [“c”] = 3,  
}  
print(table.maxn(tbtest))  
print(#(tbtest)) 
复制代码

那么打印的结果就都是0了。

换句话说,事实上因为lua中table的构造表达式非常灵活,在同一个table中,你可以随意定义各种你想要的内容,比如:

复制代码
tbtest = {  
    [1] = 1,  
    [2] = 2,  
    [3] = 3,  
    ["a"] = 4,  
    ["b"] = 5,  
} 
复制代码

同时由于这个灵活性,你也没有办法获取整个table的长度,其实在coding的过程中,你会发现,你真正想要获取整个table长度的地方几乎没有,你总能采取一种非常巧妙的定义方式,把这种需要获取整个table长度的操作避免掉,比如:

复制代码
tbtest = {  
    tbaaa = {  
        [1] = 1,  
        [2] = 2,  
        [3] = 3,  
    },  
    ["a"] = 4,  
    ["b"] = 5,  
} 
复制代码

你可能会惊讶,上面这种table该如何遍历呢?

for k, v in pairs(tbtest) do  
    print(k, v)  
end 

输出是:a 4 b 5 tbaaa table:XXXXX。

由此你可以看到,其实在table中定义一个table,这个table的名字就是key,对应的内容其实是table的地址。

当然,如果你用

for k, v in ipairs(tbtest) do  
    print(k,v)  
end 

来遍历的话,就什么都不会打印,因为没有key为1的值。但当你增加一个key为1的值时,ipairs只会打印那一个值,现在你明白ipairs是如何工作的吧。

既然这里谈到了遍历,就说一下目前看到的几种针对table的遍历方式:

for i=1, #tbtest do --这种方式无法遍历所有的元素,因为'#'只会获取tbtest中从key为1开始的key连续的那几个元素,如果没有key为1,那么这个循环将无法进入

for i=1, table.maxn(tbtest) do --这种方式同样无法遍历所有的元素,因为table.maxn只会获取key为整数中最大的那个数,遍历的元素其实是查找tbtest[1]~tbtest[整数key中最大值],所以,对于string做key的元素不会去查找,而且这么查找的效率低下,因为如果你整数key中定义的最大的key是10000,然而10000以下的key没有几个,那么这么遍历会浪费很多时间,因为会从1开始直到10000每一个元素都会查找一遍,实际上大多数元素都是不存在的,比如:

复制代码
tbtest = {  
    [1] = 1,  
    [10000] = 2,  
}  
local count = 0  
for i=1, table.maxn(tbtest) do  
    count = count + 1  
    print(tbtest[i])  
end  
print(count) 
复制代码

你会看到打印结果是多么的坑爹,只有1和10000是有意义的,其他的全是nil,而且count是10000。耗时非常久。一般我不这么遍历。但是有一种情况下又必须这么遍历,这个在我的工作中还真的遇到了,这是后话,等讲完了再谈。

for k, v in pairs(tbtest) do 

这个是唯一一种可以保证遍历tbtest中每一个元素的方式,别高兴的太早,这种遍历也有它自身的缺点,就是遍历的顺序不是按照tbtest定义的顺序来遍历的,这个前面讲到过,当然,对于不需要顺序遍历的用法,这个是唯一可靠的遍历方式。

for k, v in ipairs(tbtest) do 

这个只会遍历tbtest中key为整数,而且必须从1开始的那些连续元素,如果没有1开始的key,那么这个遍历是无效的,我个人认为这种遍历方式完全可以被改造table和for i=1, #(tbtest) do的方式来代替,因为ipairs的效果和'#'的效果,在遍历的时候是类似的,都是按照key的递增1顺序来遍历。

好,再来谈谈为什么我需要使用table.maxn这种非常浪费的方式来遍历,在工作中, 我遇到一个问题,就是需要把当前的周序,转换成对应的奖励,简单来说,就是从一个活动开始算起,每周的奖励都不是固定的,比如1~4周给一种奖励,5~8周给另一种奖励,或者是一种排名奖励,1~8名给一种奖励,9~16名给另一种奖励,这种情况下,我根据长久的C语言的习惯,会把table定义成这个样子:

tbtestAward = {  
    [8] = 1,  
    [16] = 3,  
} 

这个代表,1~8给奖励1,9~16给奖励3。这样定义的好处是奖励我只需要写一次(这里的奖励用数字做了简化,实际上奖励也是一个大的table,里面还有非常复杂的结构)。然后我就遇到一个问题,即我需要根据周序数,或者是排名序数来确定给哪一种奖励,比如当前周序数是5,那么我应该给我定义好的key为8的那一档奖励,或者当前周序数是15,那么我应该给奖励3。由此读者看出,其实我定义的key是一个分界,小于这个key而大于上一个key,那么就给这个key的奖励,这就是我判断的条件。逻辑上没有问题,但是lua的遍历方式却把我狠狠地坑了一把。读者可以自己想一想我上面介绍的4种遍历方式,该用哪一种来实现我的这种需求呢?这个函数的大致框架如下:

复制代码
function GetAward(nSeq)  
    for 遍历整个奖励表 do  
        if 满足key的条件 then  
            return 返回对应奖励的key  
        end  
    end  
    return nil  
end 
复制代码

我也不卖关子了,分别来说一说吧,首先因为我的key不是连续的,而且没有key为1的值,所以ipairs和'#'遍历是没用的。这种情况下理想的遍历貌似是pairs,因为它会遍历我的每一个元素,但是读者不要忘记了,pairs遍历并非是按照我定义的顺序来遍历,如果我真的使用的条件是:序数nSeq小于这个key而大于上一个key,那么就返回这个key。那么我无法保证程序执行的正确性,因为key的顺序有可能是乱的,也就是有可能先遍历到的是key为16的值,然后才是key为8的值。

这么看来我只剩下table.maxn这么一种方式了,于是我写下了这种代码:

复制代码
for i=1, table.maxn(tbtestAward) do  
    if tbtestAward[i] ~= nil then  
        if nSeq <= i then  
            return i  
        end  
    end  
end  
复制代码

这么写效率确实低下,因为实际上还是遍历了从key为1开始直到key为table.maxn中间的每一个值,不过能够满足我上面的要求。当时我是这么实现的,因为这个奖励表会不断的发生变化,这样我每次修改只需要修改这个奖励表就能够满足要求了,后来我想了想,觉得其实我如果自己再定义一个序数转换成对应的奖励数种类的表就可以避免这种坑爹的操作了,不过如果奖励发生修改,我需要统一排查的地方就不止这个奖励表了,权衡再三,我还是没有改,就这么写了。没办法,不断变化的需求已经把我磨练的忘记了程序的最高理想。我甚至愿意牺牲算法的效率而去追求改动的稳定性。在此哀悼程序员的无奈。我这种时间换空间的做法确实不知道好不好。

后来我在《Programming In Lua》中看到了一个神奇的迭代器,使用它就可以达到我想要的这种遍历方式,而且不需要去遍历那些不存在的key。它的方法是把你所需要遍历的table里的key按照遍历顺序放到另一个临时的table中去,这样只需要遍历这个临时的table按顺序取出原table中的key就可以了。如下:

首先定义一个迭代器:

复制代码
function pairsByKeys(t)  
    local a = {}  
    for n in pairs(t) do  
        a[#a+1] = n  
    end  
    table.sort(a)  
    local i = 0  
    return function()  
        i = i + 1  
        return a[i], t[a[i]]  
    end  
end 
复制代码

然后在遍历的时候使用这个迭代器就可以了,table同上,遍历如下:

for key, value in pairsByKeys(tbtestAward) do  
    if nSeq <= key then  
        return key  
    end  
end 

并且后来我发现有了这个迭代器,我根本不需要先做一步获取是哪一档次的奖励的操作,直接使用这个迭代器进行发奖就可以了。大师就是大师,我怎么就没想到呢!

还有些话我还没有说,比如上面数值型遍历也并非是像看起来那样进行遍历的,比如下面的遍历:

复制代码
tbtest = {  
    [1] = 1,  
    [2] = 2,  
    [3] = 3,  
    [5] = 5,  
} 
 
for i=1, #(tbtest) do  
    print(tbtest[i])  
end 
复制代码

打印的顺序是:1,2,3。不会打印5,因为5已经不在table的数组数据块中了,我估计是被放到了hash数据块中,但是当我修改其中的一些key时,比如:

复制代码
tbtest = {  
    [1] = 1,  
    [2] = 2,  
    [4] = 4,  
    [5] = 5,  
} 
 
for i=1, #(tbtest) do  
    print(tbtest[i])  
end 
复制代码

打印的内容却是:1,2,nil,4,5。这个地方又遍历到了中间没有的key值,并且还能继续遍历下去。

<think>好的,我现在需要回答用户关于如何遍历Lua table的问题。首先,我得回忆一下Luatable的基本结构,以及常用的遍历方法。Luatable有数部分和哈希表部分,所以遍历方式可能不同。 用户可能想知道如何遍历部分和键值对。我记得常用的有ipairs和pairs函数。ipairs用于顺序遍历部分,而pairs可以遍历所有元素。不过需要确认这两者的具体区别,比如ipairs是否在遇到nil时停止,而pairs是否包括非数字键。 接下来,我应该举例说明。比如,数遍历可以用ipairs,而哈希表用pairs。同时,用户可能还想知道如何按特定顺序遍历,比如按键排序后的顺序。这时候可能需要先用table.keys获取所有键,然后排序,再遍历。 需要注意错误的使用方法,比如用pairs遍历,虽然可行,但顺序可能不保证。或者用ipairs遍历混合table,可能漏掉非数字键的元素。另外,用户可能关心性能问题,比如ipairs和pairs的效率差异,但一般来说在Lua中这两者效率接近,但最好根据使用场景选择。 还要提到通用遍历方法,比如使用next函数手动控制迭代,不过这种情况较少见。用户可能需要基本用法,所以重点放在ipairs和pairs的区别和示例上。 最后,总结不同情况下的推荐方法,确保用户能根据需求选择合适的遍历方式。可能还要提醒,Lua的数索引默认从1开始,这点在遍历时需要注意,避免出现off-by-one错误。</think>在 Lua 中,遍历 table 主要有以下几种方式,我们通过具体示例逐步说明: ### 1. 遍历部分(顺序索引) 使用 `ipairs` 函数遍历连续数字索引的数元素,遇到 `nil` 会停止: ```lua local arr = {"apple", "banana", "cherry"} for i, v in ipairs(arr) do print(i, v) -- 输出:1 apple → 2 banana → 3 cherry end ``` ### 2. 遍历所有元素(键值对) 使用 `pairs` 函数遍历 table 的所有键值对(包括非数字键): ```lua local dict = { name = "Alice", age = 30, [5] = "特殊键", "数元素" } for k, v in pairs(dict) do print(k, v) -- 可能输出:1 数元素 → age 30 → name Alice → 5 特殊键 end ``` ### 3. 按特定顺序遍历 如果需要有序遍历,需先提取键并排序: ```lua local t = {z=3, a=1, b=2} local keys = {} -- 提取所有键 for k in pairs(t) do table.insert(keys, k) end -- 排序键 table.sort(keys) -- 按排序后的键遍历 for _, k in ipairs(keys) do print(k, t[k]) -- 输出:a 1 → b 2 → z 3 end ``` ### 4. 遍历注意事项 - `ipairs` 和 `pairs` 的区别: - `ipairs`:遍历连续数字索引(1,2,3...),遇到 `nil` 停止 - `pairs`:遍历所有键值对(顺序不保证) - 混合类型 table遍历: ```lua local mixed = { "数元素1", [3] = "数字键3", key = "字符串键", "数元素2" } -- ipairs 遍历(只输出索引1和2) for i,v in ipairs(mixed) do print(i,v) -- 输出:1 数元素1 → 2 数元素2 end -- pairs 遍历所有元素 for k,v in pairs(mixed) do print(k,v) -- 输出可能包含:1、2、3、"key" end ``` ### 5. 性能优化技巧 - 数优先:需要高效遍历时,优先使用数字索引数 - 避免修改:遍历过程中不要修改表结构(增删元素) - 缓存长度:对于大数可先保存长度 `local n = #t` 选择遍历方式: - 纯数 → 用 `ipairs` - 需要所有元素 → 用 `pairs` - 需要有序遍历 → 先提取键并排序 可以通过以下命令验证遍历效果: ```bash lua -e 't = {a=1,b=2}; for k,v in pairs(t) do print(k,v) end' ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值