1. 程式人生 > >Lua基礎之table詳解

Lua基礎之table詳解

概要:1.table特性;2.table的構造;3.table常用函式;4.table遍歷;5.table面向物件

1.table特性

table是一個“關聯陣列”,陣列的索引可以是數字或者是字串,所有索引值都需要用"[""]"括起來;如果是字串,還可以去掉引號和中括號; 即如果沒有[]括起,則認為是字串索引

table 的預設初始索引一般以 1 開始,如果不寫索引,則索引就會被認為是數字,並按順序自動從1往後編;

table 的變數只是一個地址引用,對 table 的操作不會產生資料影響

table 不會固定長度大小,有新資料插入時長度會自動增長

table 裡儲存資料可以是任何型別,包括function和table

table所有元素之間,總是用逗號""隔開;

2.table的構造

建立table

t = {}			--定義一個空表
t["jun"] = 6	--字串key值的屬性
t[1] = 1		--數字key值的屬性
t.jun = 16		--字串key值的簡寫
t.test = {num=28,str="test"}	--屬性可以是table
print(t.test.num)				--輸出28
t.testFunction = function() print("函式") end  --屬性可以是function
t.testFunction()				--呼叫函式
t:testFunction()				--同上

--上面的table還可以這麼寫
t=
{
	1,
	jun = 6,
	test=
	{
		num = 28,
		str = "test",
	}
	testFunction = function() print("函式") end,
}


3.table常用函式

table.pack(...)

獲取一個索引從 1 開始的引數表 table,並會對這個 table 預定義一個欄位 n,表示該表的長度

function table_pack(param, ...)
    local arg = table.pack(...)
    print("this arg table length is", arg.n)
    for i = 1, arg.n do
        print(i, arg[i])
    end
end
 
table_pack("test", "param1", "param2", "param3")

table.concat(table, sep,  start, end)

table.concat()函式列出引數中指定table的陣列部分從start位置到end位置的所有元素, 元素間以指定的分隔符(sep)隔開。除了table外, 其他的引數都不是必須的, 分隔符的預設值是空字元, start的預設值是1, end的預設值是陣列部分的總長.

sep, start, end這三個引數是順序讀入的, 所以雖然它們都不是必須引數, 但如果要指定靠後的引數, 必須同時指定前面的引數.

lua 中字串的儲存方式與 C 不一樣,lua 中的每個字串都是單獨的一個拷貝,拼接兩個字串會產生一個新的拷貝,如果拼接操作特別多,就會影響效能,所以對於密集型的字元並接,table.concat 比用 ".." 連線更高效。

local tbl = {"apple", "pear", "orange", "grape"}
 
print(table.concat(tbl))
 
print(table.concat(tbl, "、"))
 
print(table.concat(tbl, "、", 2))
 
print(table.concat(tbl, "、", 2, 3))

table.unpack(table, start, end)

用於返回 table 裡的元素,引數 start 是開始返回的元素位置,預設是 1,引數 end 是返回最後一個元素的位置,預設是 table 最後一個元素的位置,引數 start、end 都是可選
local tbl = {"apple", "pear", "orange", "grape"}
print(table.unpack(tbl))
 
local a, b, c, d = table.unpack(tbl)
print(a, b, c, d)
 
print(table.unpack(tbl, 2))
print(table.unpack(tbl, 2, 3))
table.maxn(table)

返回指定table中所有正數key值中最大的key值. 如果不存在key值為正數的元素, 則返回0. 此函式不限於table的陣列部分.

tbl = {[1] = "a", [2] = "b", [3] = "c", [26] = "z"}
print(#tbl)           --3因為26和之前的數字不連續, 所以不算在陣列部分內
print(table.maxn(tbl))	 --26
tbl[91.32] = true
print(table.maxn(tbl))	--91.32


table.getn(table)

返回table中元素的個數

t1 = {1, 2, 3, 5};
print(getn(t1))
--4

table.insert(table, pos, value)

用於向 table 的指定位置(pos)插入值為value的一個元素. pos引數可選, 預設為陣列部分末尾.

local tbl = {"apple", "pear", "orange", "grape"}
 
table.insert(tbl, "watermelon")
print(table.concat(tbl, "、"))
 
table.insert(tbl, 2, "watermelon")
print(table.concat(tbl, "、"))

table.remove(table, pos)

刪除並返回table陣列部分位於pos位置的元素. 其後的元素會被前移. pos引數可選, 預設為table長度, 即從最後一個元素刪起,並且引數 pos 的型別只能是數字 number 型別。

local tbl = {"apple", "pear", "orange", "grape"}
table.remove(tbl, 2)
print(table.concat(tbl, "、"))
 
table.remove(tbl)
print(table.concat(tbl, "、"))

table.sort(table, comp)

用於對 table 裡的元素作排序操作,引數 comp 是一個排序對比函式,它有兩個引數 param1、param2,如果 param1 排在 param2 前面,那麼排序函式返回 true,否則返回 false。引數 comp 可選,預設 comp 的情況下是對錶作升序排序。

local tbl = {"apple", "pear", "orange", "grape"}
local sort_func1 = function(a, b) return a > b end
table.sort(tbl, sort_func1)
print(table.concat(tbl, "、"))
 
local sort_func2 = function(a, b) return a < b end
table.sort(tbl, sort_func2)
print(table.concat(tbl, "、"))
local tbl = {"apple", "pear", "orange", "grape"}
table.sort(tbl)
print(table.concat(tbl, "、"))


table.foreachi(table, function(i, v))

會期望一個從 1(數字 1)開始的連續整數範圍,遍歷table中的key和value逐對進行function(i, v)操作

t1 = {2, 4, 6, language="Lua", version="5", 8, 10, 12, web="hello lua"};
table.foreachi(t1, function(i, v) print (i, v) end) ; 
--結果1 2
--2 4
--3 6
--4 8
--5 10
--6 12

table.foreach(table, function(i, v))

與foreachi不同的是,foreach會對整個表進行迭代

t1 = {2, 4, 6, language="Lua", version="5", 8, 10, 12, web="hello lua"};
table.foreach(t1, function(i, v) print (i, v) end) ;
--[[
輸出結果:
1 2
2 4
3 6
4 8
5 10
6 12
web hello lua
language Lua
version 5
]]

4.table遍歷

for key, value in pairs(tbtest) do      
     print(value)
end  

這樣的遍歷順序並非是tbtest中table的排列順序,而是根據tbtest中key的hash值排列的順序來遍歷的。 for key, value in ipairs(tbtest) do
     print(value)
end  
這樣的迴圈必須要求tbtest中的key為順序的,而且必須是從1開始,ipairs只會從1開始按連續的key順序遍歷到key不連續為止。

5.table面向物件

和編譯型的面嚮物件語言不同,在lua中不存在類的定義這樣一個概念,不管是類的定義還是類的例項都需要通過lua table來模擬。

Test = {jun = 0}
function Test.withdraw(self,v)
     self.jun = self.jun - v
 end
 --下面是程式碼的呼叫:
a = Test
a.withdraw(a,10)
Lua提供了一種更為便利的語法,即將點(.)替換為冒號(:),這樣可以在定義和呼叫時均隱藏self引數
function Test:withdraw(v)
     self.jun = self.jun - v
 end
a = Test
a:withdraw(10)

Lua 沒有類的概念,不過可以通過元表(metatable)來實現與原型 prototype 類似的功能,而 prototype 與類的工作機制一樣,都是定義了特定物件行為。Lua 裡的原型特性主要使用元表的 __index 事件來實現,這樣當呼叫物件沒定義的方法時,會向其元表的 __index 鍵(事件)查詢。例如有 a 和 b 兩個物件,想讓 b 作為 a 的原型 prototype,只需要把 b 設定為 a 元表的 __index 值就行:當物件 a 呼叫任何不存在的成員都會到物件 b 中查詢,a 可以擁有或呼叫 b 的屬性或方法,從某種意義上看,b 可以看作是一個類,a 是 b 的物件。

--[[
在這段程式碼中,我們可以將Account視為class的宣告,如Java中的:
public class Account 
{
    public float balance = 0;
    public Account(Account o);
    public void deposite(float f);
}
--]]
--這裡balance是一個公有的成員變數。
Account = {balance = 0}

--new可以視為建構函式
function Account:new(o)
    o = o or {} --如果引數中沒有提供table,則建立一個空的。
    --將新物件例項的metatable指向Account表(類),這樣就可以將其視為模板了。
    setmetatable(o,self)
    --在將Account的__index欄位指向自己,以便新物件在訪問Account的函式和欄位時,可被直接重定向。
    self.__index = self
    --最後返回構造後的物件例項
    return o
end

--deposite被視為Account類的公有成員函式
function Account:deposit(v)
    --這裡的self表示物件例項本身
    self.balance = self.balance + v
end

--下面的程式碼建立兩個Account的物件例項

--通過Account的new方法構造基於該類的示例物件。
a = Account:new()
--[[
這裡需要具體解釋一下,此時由於table a中並沒有deposite欄位,因此需要重定向到Account,
同時呼叫Account的deposite方法。在Account.deposite方法中,由於self(a物件)並沒有balance
欄位,因此在執行self.balance + v時,也需要重定向訪問Account中的balance欄位,其預設值為0。
在得到計算結果後,再將該結果直接賦值給a.balance。此後a物件就擁有了自己的balance欄位和值。
下次再呼叫該方法,balance欄位的值將完全來自於a物件,而無需在重定向到Account了。
--]]
a:deposit(100.00)
print(a.balance) --輸出100

b = Account:new()
b:deposit(200.00)
print(b.balance) --輸出200


繼承

--需要說明的是,這段程式碼僅提供和繼承相關的註釋,和類相關的註釋在上面的程式碼中已經給出。
Account = {balance = 0}

function Account:new(o)
    o = o or {}
    setmetatable(o,self)
    self.__index = self
    return o
end

function Account:deposit(v)
    self.balance = self.balance + v
end

function Account:withdraw(v)
    if v > self.balance then
        error("Insufficient funds")
    end
    self.balance = self.balance - v
end

--下面將派生出一個Account的子類,以使客戶可以實現透支的功能。
SpecialAccount = Account:new()  --此時SpecialAccount仍然為Account的一個物件例項

--派生類SpecialAccount擴展出的方法。
--下面這些SpecialAccount中的方法程式碼(getLimit/withdraw),一定要位於SpecialAccount被Account構造之後。
function SpecialAccount:getLimit()
    --此時的self將為物件例項。
    return self.limit or 0
end

--SpecialAccount將為Account的子類,下面的方法withdraw可以視為SpecialAccount
--重寫的Account中的withdraw方法,以實現自定義的功能。
function SpecialAccount:withdraw(v)
    --此時的self將為物件例項。
    if v - self.balance >= self:getLimit() then
        error("Insufficient funds")
    end
    self.balance = self.balance - v
end

--在執行下面的new方法時,table s的元表已經是SpecialAccount了,而不再是Account。
s = SpecialAccount:new{limit = 1000.00}
--在呼叫下面的deposit方法時,由於table s和SpecialAccount均未提供該方法,因此訪問的仍然是
--Account的deposit方法。
s:deposit(100)


--此時的withdraw方法將不再是Account中的withdraw方法,而是SpecialAccount中的該方法。
--這是因為Lua先在SpecialAccount(即s的元表)中找到了該方法。
s:withdraw(200.00)
print(s.balance) --輸出-100

私密性

私密性對於面嚮物件語言來說是不可或缺的,否則將直接破壞物件的封裝性。Lua作為一種面向過程的指令碼語言,更是沒有提供這樣的功能,然而和模擬支援類與繼承一樣,我們仍然可以在Lua中通過特殊的程式設計技巧來實現它,這裡我們應用的是Lua中的閉包函式。

--這裡我們需要一個閉包函式作為類的建立工廠
function newAccount(initialBalance)
    --這裡的self僅僅是一個普通的區域性變數,其含義完全不同於前面示例中的self。
    --這裡之所以使用self作為區域性變數名,也是為了方便今後的移植。比如,以後
    --如果改為上面的實現方式,這裡應用了self就可以降低修改的工作量了。
    local self = {balance = initialBalance} --這裡我們可以將self視為私有成員變數
    local withdraw = function(v) self.balance = self.balance - v end
    local deposit = function(v) self.balance = self.balance + v end
    local getBalance = function() return self.balance end
    --返回物件中包含的欄位僅僅為公有方法。事實上,我們通過該種方式,不僅可以實現
    --成員變數的私有性,也可以實現方法的私有性,如:
    --local privateFunction = function() --do something end
    --只要我們不在輸出物件中包含該方法的欄位即可。
    return {withdraw = withdraw, deposit = deposit, getBalance = getBalance}
end

--和前面兩個示例不同的是,在呼叫物件方法時,不再需要self變數,因此我們可以直接使用點(.),
--而不再需要使用冒號(:)操作符了。
accl = newAccount(100.00)
--在函式newAccount返回之後,該函式內的“非區域性變數”表self就不再能被外部訪問了,只能通過
--該函式返回的物件的方法來操作它們。
accl.withdraw(40.00)
print(acc1.getBalance())