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())