前言
最近在嘗試配置 awesome WM,因此粗略地學習了一下 lua 。 在學習過程中,我完全被 table 在 lua 中的應用所鎮住了。
table 在 lua 中真的是無處不在:首先,它可以作為字典和數組來用; 此外,它還可以被用于設置閉包環境、module; 甚至可以用來模擬對象和類
字典
table 最基礎的作用就是當成字典來用。 它的 key 值可以是除了 nil 之外的任何類型的值。
t={}
t[{}] = "table" -- key 可以是 table
t[1] = "int" -- key 可以是整數
t[1.1] = "double" -- key 可以是小數
t[function () end] = "function" -- key 可以是函數
t[true] = "Boolean" -- key 可以是布爾值
t["abc"] = "String" -- key 可以是字符串
t[io.stdout] = "userdata" -- key 可以是userdata
t[coroutine.create(function () end)] = "Thread" -- key可以是thread
當把 table 當成字典來用時,可以使用 pairs 函數來進行遍歷。
for k,v in pairs(t) do
print(k,"->",v)
end
運行結果為:
1 -> int
1.1 -> double
thread: 0x220bb08 -> Thread
table: 0x220b670 -> table
abc -> String
file (0x7f34a81ef5c0) -> userdata
function: 0x220b340 -> function
true -> Boolean
從結果中你還可以發現,使用 pairs 進行遍歷時的順序是隨機的,事實上相同的語句執行多次得到的結果是不一樣的。
table 中的key最常見的兩種類型就是整數型和字符串類型。 當 key 為字符串時,table可以當成結構體來用。同時形如 t["field"] 這種形式的寫法可以簡寫成 t.field 這種形式。
數組
當 key 為整數時,table 就可以當成數組來用。而且這個數組是一個 索引從1開始 ,沒有固定長度,可以根據需要自動增長的數組。
a = {}
for i=0,5 do -- 注意,這里故意寫成了i從0開始
a[i] = 0
end
當將 table 當成數組來用時,可以通過 長度操作符 # 來獲取數組的長度
結果為
5
你會發現, lua 認為 數組 a 中只有5個元素,到底是哪5個元素呢?我們可以使用使用 ipairs 對數組進行遍歷:
for i,v in ipairs(a) do
print(i,v)
end
結果為
1 0
2 0
3 0
4 0
5 0
從結果中你會發現 a 的0號索引并不認為是數組中的一個元素,從而也驗證了 lua 中的數組是從 1開始索引的
另外,將table當成數組來用時,一定要注意索引不連貫的情況,這種情況下 # 計算長度時會變得很詭異
a = {}
for i=1,5 do
a[i] = 0
end
a[8] = 0 -- 雖然索引不連貫,但長度是以最大索引為準
print(#a)
a[100] = 0 -- 索引不連貫,而且長度不再以最大索引為準了
print(#a)
結果為:
8
8
而使用 ipairs 對數組進行遍歷時,只會從1遍歷到索引中斷處
for i,v in ipairs(a) do
print(i,v)
end
結果為:
1 0
2 0
3 0
4 0
5 0
環境(命名空間)
lua將所有的全局變量/局部變量保存在一個常規table中,這個table一般被稱為全局或者某個函數(閉包)的環境。
為了方便,lua在創建最初的全局環境時,使用全局變量 _G 來引用這個全局環境。因此,在未手工設置環境的情況下,可以使用 _G[varname] 來存取全局變量的值.
for k,v in pairs(_G) do
print(k,"->",v)
end
rawequal -> function: 0x41c2a0
require -> function: 0x1ea4e70
_VERSION -> Lua 5.3
debug -> table: 0x1ea8ad0
string -> table: 0x1ea74b0
xpcall -> function: 0x41c720
select -> function: 0x41bea0
package -> table: 0x1ea4820
assert -> function: 0x41cc50
pcall -> function: 0x41cd10
next -> function: 0x41c450
tostring -> function: 0x41be70
_G -> table: 0x1ea2b80
coroutine -> table: 0x1ea4ee0
unpack -> function: 0x424fa0
loadstring -> function: 0x41ca00
setmetatable -> function: 0x41c7e0
rawlen -> function: 0x41c250
bit32 -> table: 0x1ea8fc0
utf8 -> table: 0x1ea8650
math -> table: 0x1ea7770
collectgarbage -> function: 0x41c650
rawset -> function: 0x41c1b0
os -> table: 0x1ea6840
pairs -> function: 0x41c950
arg -> table: 0x1ea9450
table -> table: 0x1ea5130
tonumber -> function: 0x41bf40
io -> table: 0x1ea5430
loadfile -> function: 0x41cb10
error -> function: 0x41c5c0
load -> function: 0x41ca00
print -> function: 0x41c2e0
dofile -> function: 0x41cbd0
rawget -> function: 0x41c200
type -> function: 0x41be10
getmetatable -> function: 0x41cb80
module -> function: 0x1ea4e00
ipairs -> function: 0x41c970
從lua 5.2開始,可以通過修改 _ENV 這個值(lua5.1中的setfenv從5.2開始被廢除)來設置某個函數的環境,從而讓這個函數中的執行語句在一個新的環境中查找全局變量的值。
a=1 -- 全局變量中a=1
local env={a=10,print=_G.print} -- 新環境中a=10,并且確保能訪問到全局的print函數
function f1()
local _ENV=env
print("in f1:a=",a)
a=a*10 -- 修改的是新環境中的a值
end
f1()
print("globally:a=",a)
print("env.a=",env.a)
in f1:a= 10
globally:a= 1
env.a= 100
另外,新創建的閉包都繼承了創建它的函數的環境
module
lua 中的模塊也是通過返回一個table來供模塊使用者來使用的。 這個 table中包含的是模塊中所導出的所有東西,包括函數和常量。
定義module的一般模板為
module(模塊名, package.seeall)
其中 module(模塊名) 的作用類似于
local modname = 模塊名
local M = {} -- M即為存放模塊所有函數及常數的table
_G[modname] = M
package.loaded[modname] = M
setmetatable(M,{__index=_G}) -- package.seeall可以使全局環境_G對當前環境可見
local _ENV = M -- 設置當前的運行環境為 M,這樣后續所有代碼都不需要限定模塊名了,所定義的所有函數自動變成M的成員
函數定義以及常量定義>
return M -- module函數會幫你返回module table,而無需手工返回
對象
lua 中之所以可以把table當成對象來用是因為:
函數在 lua 中是一類值,你可以直接存取table中的函數值。 這使得一個table既可以有自己的狀態,也可以有自己的行為:
Account = {balance = 0}
function Account.withdraw(v)
Account.balance = Account.balance - v
end
lua 支持閉包,這個特性可以用來模擬對象的私有成員變量
function new_account(b)
local balance = b
return {withdraw = function (v) balance = balance -v end,
get_balance = function () return balance end
}
end
a1 = new_account(1000)
a1.withdraw(10)
print(a1.get_balance())
990
不過,上面第一種定義對象的方法有一個缺陷,那就是方法與 Account 這個名稱綁定死了。 也就是說,這個對象的名稱必須為 Accout 否則就會出錯
a = Account
Account = nil
a.withdraw(10) -- 會報錯,因為Accout.balance不再存在
為了解決這個問題,我們可以給 withdraw 方法多一個參數用于指向對象本身
Account = {balance=100}
function Account.withdraw(self,v)
self.balance = self.balance - v
end
a = Account
Account = nil
a.withdraw(a,10) -- 沒問題,這個時候 self 指向的是a,因此會去尋找 a.balance
print(a.balance)
90
不過由于第一個參數 self 幾乎總是指向調用方法的對象本身,因此 lua 提供了一種語法糖形式 object:method(...) 用于隱藏 self 參數的定義及傳遞. 這里冒號的作用有兩個,其在定義函數時往函數中地一個參數的位置添加一個額外的隱藏參數 sef, 而在調用時傳遞一個額外的隱藏參數 self 到地一個參數位置。 即 function object:method(v) end
等價于 function object.method(self,v) end, object:method(v)
等價于 object.method(object,v)
類
當涉及到類和繼承時,就要用到元表和元方法了。事實上,對于 lua 來說,對象和類并不存在一個嚴格的劃分。
當一個對象被另一個table的 __index 元方法所引用時,table就能引用該對象中所定義的方法,因此也就可以理解為對象變成了table的類。
類定義的一般模板為:
function 類名:new(o)
o = o or {}
setmetatable(o,{__index = self})
return o
end
或者
function 類名:new(o)
o = o or {}
setmetatable(o,self)
self.__index = self
return o
end
相比之下,第二種寫法可以多省略一個table
另外有一點我覺得有必要說明的就是 lua 中的元方法是在元表中定義的,而不是對象本身定義的,這一點跟其他面向對象的語言比較不同。
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
您可能感興趣的文章:- Lua Table轉C# Dictionary的方法示例
- Lua中設置table為只讀屬性的方法詳解
- Lua編程示例(一):select、debug、可變參數、table操作、error
- 舉例講解Lua中的Table數據結構
- Lua table中安全移除元素的方法
- Lua的table庫函數insert、remove、concat、sort詳細介紹
- C++遍歷Lua table的方法實例
- Lua中釋放table占用內存的方法
- Lua中table的遍歷詳解
- Lua中獲取table長度問題探討
- Lua中獲取table長度的方法
- Lua中table里內嵌table的例子
- Lua面向對象編程之基礎結構table簡例