亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > 綜合 > 正文

Lua中table的遍歷詳解

2024-07-21 23:04:39
字體:
來源:轉載
供稿:網友

當我在工作中使用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

前兩種是泛型遍歷,后兩種是數值型遍歷。當然你還會說lua的table遍歷還有很多種方法啊,沒錯,不過最常見的這些遍歷確實有必要弄清楚。

 

這四種方式各有特點,由于在工作中我幾乎每天都會使用遍歷table的方法,一開始也非常困惑這些方式的不同,一段時間后才漸漸明白,這里我也是把自己的一點經驗告訴大家,對跟我一樣的lua初學者也許有些幫助(至少當初我在寫的時候在網上就找了很久,不知道是因為大牛們都認為這些很簡單,不需要說,還是因為我笨,連這都要問)。

首先要明確一點,就是lua中table并非像是C/C++中的數組一樣是順序存儲的,準確來說lua中的table更加像是C++中的map,通過Key對應存儲Value,但是并非順序來保存key-value對,而是使用了hash的方式,這樣能夠更加快速的訪問key對應的value,我們也知道hash表的遍歷需要使用所謂的迭代器來進行,同樣,lua也有自己的迭代器,就是上面4種遍歷方式中的pairs和ipairs遍歷。但是lua同時提供了按照key來遍歷的方式(另外兩種,實質上是一種),正式因為它提供了這種按key的遍歷,才造成了我一開始的困惑,我一度認為lua中關于table的遍歷是按照我table定義key的順序來的。
下面依次來講講四種遍歷方式,首先來看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值,并且還能繼續遍歷下去。我最近正在看lua源碼中table的實現部分,已經明白了是怎么回事,不過我想等我能夠更加清晰的闡述lua中table的實現過程了再向大家介紹。用我師傅的話說就是不要使用一些未定義的行為方法,避免在工作中出錯,不過工作外,我還是希望能明白未定義的行為中那些必然性,o(︶︿︶)o 唉!因果論的孩子傷不起。等我下一篇博文分析lua源碼中table的實現就能夠更加清晰的說明這些了。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
久久777国产线看观看精品| 色妞在线综合亚洲欧美| 成人福利视频在线观看| 欧美日本高清视频| 精品视频在线播放免| 国产精品成av人在线视午夜片| 日韩中文在线中文网三级| 91精品视频网站| 亚洲淫片在线视频| 中文字幕日韩欧美| 久久av在线看| 都市激情亚洲色图| 色婷婷av一区二区三区在线观看| 青草青草久热精品视频在线网站| 韩国精品久久久999| 久久深夜福利免费观看| 久久久久久九九九| 久久久久久国产精品三级玉女聊斋| 北条麻妃一区二区在线观看| 欧美刺激性大交免费视频| 北条麻妃99精品青青久久| 亚洲视频网站在线观看| 欧美性猛交xxxxx水多| 亚洲综合精品伊人久久| 日韩成人在线视频观看| 欧美激情网站在线观看| 久久网福利资源网站| 亚洲视频在线观看网站| 精品一区二区三区四区在线| 国产专区精品视频| 亚洲专区中文字幕| 成人欧美一区二区三区在线湿哒哒| 欧美日韩在线视频一区二区| 亚洲成人黄色在线观看| 丝袜情趣国产精品| 欧美激情乱人伦一区| 亚洲欧美日韩另类| 亚洲成人免费在线视频| 91免费的视频在线播放| 国产精品稀缺呦系列在线| 亚洲2020天天堂在线观看| 国产成人综合一区二区三区| 青青a在线精品免费观看| 2018日韩中文字幕| 欧美激情综合色| 日韩欧美在线免费观看| 亚洲free性xxxx护士hd| 91国在线精品国内播放| 久久99热精品这里久久精品| 91av在线免费观看| 久久香蕉国产线看观看av| 欧美视频一二三| 国内外成人免费激情在线视频网站| 91精品国产777在线观看| 欧美激情精品久久久久久黑人| 91免费国产网站| 亚洲丝袜一区在线| 色无极亚洲影院| 蜜月aⅴ免费一区二区三区| 亚洲美女av在线播放| 91高清视频免费观看| 亚洲一区二区三区香蕉| 日韩美女免费观看| 欧美精品免费播放| 欧美人成在线视频| 日韩精品久久久久| 91免费人成网站在线观看18| 亚洲色图18p| 日韩av电影中文字幕| 国产中文字幕日韩| 欧美在线观看一区二区三区| 欧美亚洲另类在线| 庆余年2免费日韩剧观看大牛| 国产区亚洲区欧美区| 国产精品久久久久久久久久尿| 日韩在线观看免费全集电视剧网站| 欧美性生交大片免费| 日韩在线播放av| 91在线播放国产| 精品久久久久久久久久| 欧美日韩免费区域视频在线观看| 亚洲已满18点击进入在线看片| 日韩在线欧美在线国产在线| 国产aaa精品| 日韩高清电影好看的电视剧电影| 国产精品久久久久久搜索| 亚洲美女精品久久| 中文字幕亚洲一区二区三区五十路| 日韩一二三在线视频播| 亚洲精品一区二区三区不| 欧美日韩一区二区在线播放| 国产亚洲精品成人av久久ww| 日韩精品极品视频免费观看| 久久99热这里只有精品国产| 91在线直播亚洲| 日韩欧美国产一区二区| 日韩av综合中文字幕| 日韩福利视频在线观看| 国产精品电影网| 国产精品高潮呻吟久久av无限| 自拍偷拍亚洲在线| 最近中文字幕2019免费| 欧美精品福利在线| 久久综合五月天| 日韩高清电影免费观看完整| 国产99久久精品一区二区 夜夜躁日日躁| 91中文字幕在线| 欧美日韩精品中文字幕| 亚洲社区在线观看| 国产成人精品久久二区二区| 国产精品劲爆视频| 在线观看亚洲区| 91在线观看免费| 久久99国产精品久久久久久久久| 美女999久久久精品视频| 欧美激情在线狂野欧美精品| 国产欧美日韩最新| 欧美大片在线看免费观看| 亚洲最大福利视频网| 国产丝袜精品第一页| 国产精品视频网| 亚洲第一区第二区| 亚洲乱码国产乱码精品精| 国产一区二区三区网站| 亚洲综合精品一区二区| 中文字幕国产精品久久| 日韩免费看的电影电视剧大全| 国产日韩精品电影| 欧美日韩在线看| 国产色婷婷国产综合在线理论片a| 亚洲片在线观看| 欧美激情一级精品国产| 日韩视频在线一区| 亚洲第一视频在线观看| 国产精品1234| 欧美大片免费观看在线观看网站推荐| 欧美日韩激情视频8区| 日日狠狠久久偷偷四色综合免费| 国产精品一区久久久| 国语自产精品视频在线看| 欧美极品美女视频网站在线观看免费| 日韩中文在线中文网在线观看| 久久久视频免费观看| 欧美激情视频在线观看| 精品国产一区二区三区久久| 亚洲综合中文字幕在线观看| 在线成人激情黄色| 日韩欧美在线看| 精品亚洲一区二区三区在线播放| 欧美精品制服第一页| 欧美精品18videosex性欧美| 国产欧美精品日韩精品| 成人在线激情视频| 国产91精品黑色丝袜高跟鞋| 亚洲精品国产精品国自产观看浪潮| 综合欧美国产视频二区| 欧美成人免费全部| 日韩精品在线影院| 亚洲va欧美va在线观看| 精品久久久久久电影| 国产在线播放不卡| 亚洲高清一区二| 视频在线观看一区二区| 成人性生交大片免费看视频直播|