最近要用到 Lua 編程語言,所以學習了一些簡明教程,同時記錄一下 Lua 編程語言相對于其他主流編程語言在語法上特殊的地方。其中,在 Lua 中使用Table
數據結構實現“面向對象”編程是重點。
注釋
-- 單行注釋
--[[
塊注釋,有趣的是這個注釋標記不是對稱的
--]]
變量
- 變量沒有類型,值才有類型,也就是在聲明變量的時候不需要聲明變量的類型。
- 數字只有
double
類型 。 - 沒有定義過的變量值為
nil
。 - 對于布爾類型,只有
nil
和false
表示假,其他的值都為真。 - 默認變量都是全局變量,局部變量需要加
local
關鍵字
操作符
- Lua 中沒有
++
和+=
這類的運算符 - 不等于號是
~=
- 字符串鏈接符是
..
- 條件表達的“與”、“或”、“非”分別是
and
,or
,not
控制語句
if-else 分支
i = 10
if i = 0 then
-- do something
elseif i > 5 and i < 10 then
-- do something
else
-- do something
end
while 循環
i = 0
while i < 100 do
-- do something
i = i + 1
end
until 循環
sum = 2
repeat
sum = sum ^ 2
until sum > 100
for 循環
for i=1, 100, 2 do
-- do something
end
函數
函數可以作為返回值返回
function add(x)
return function(y) y + x end
end
addOne = add(1)
addOne(1) -- 2
可以返回多個值,同時默認為全局函數。
值得注意的是 Lua 的函數參數不支持默認值,這也為 Lua 程序的重構帶來了一些麻煩。
Table
Table
是 Lua 中重要的數據結構,它是由一系列的 key-value 鍵值對組成。Lua 中的數組也是一種特殊的 Table
,它下標從 1 開始,而且在一個數組中可以有不同類型的成員。
變量對于 Table
的引用是弱引用,也就是說 Table
是獨立于變量存在的,只有當沒有任何一個變量引用這個 Table
的時候,Lua 的垃圾回收機制才會把這個 Table
從內存中回收。這個特性對于在 Lua 中實現“面向對象”也很重要。
-- 定義和訪問
person = {name = "jack", age =24}
person.name = "black"
person.age = 20
-- 另一種定義和訪問
person2 = {['name']="green", ['age']=30}
person2['name'] = "tom"
person2['age'] = 10
-- 數組
arr = {1,2,3,4,5}
arr = {[1]=1,[2]=2,[3]=3,[4]=4,[5]=5} -- 與上面的定義等價
MetaTable 和 MetaMethod
Lua 中每個值都有一套預定義的操作集合,這個集合就是 MateTable ,MetaTable 中預定義的方法就是 MetaMethod。table
和 userdata
有各自獨立的 MetaTable,從而可以利用這一特性實現“面向對象”編程。而其他類型的值則共享屬于該類型的一個 MetaTable。
面向對象
Lua 可以通過使用 Table
這種數據結構來實現面向對象編程。但是這種面向對象并不是基于類(Class
)的,而是基于原型(prototype
)的。這個原型就是我們定義好的一個 Table
,其他對象就可以通過這個原型衍生出來。
定義原型
首先定義一個 Table
當作我們的原型,并定義一個成員變量
Account = { balance = 0 }
這樣原型就定義好了,由于 Table
是獨立于變量存在的, 只要不把 Account
變量設為 nil
,那么就這個原型就一直存在。在這個原型中有一個 balance
的成員變量。
定義成員方法
緊接上面的代碼,我們可以定義一個成員方法
function Account.withdraw(self, v)
self.balance = self.balance - v
end
其中 Account.withdraw
是一個語法糖, 相當于在 Account
的 Table 中定義了一個 withdraw
的字段,而它的內容就是一個方法,上面的寫法等價于:
Account = {
withdraw = function (self, v)
self.balance = self.balance - v
end
}
在這個方法中使用了 self
關鍵字,它就是指這個 Table
本身,它有可能是這個原型,也可能是由這個原型衍生出的對象。
同時我們可以在定義成員方法時使用 :
語法糖,默認傳入 self
提高編碼效率。例如:
function Account:withdraw(v)
self.balance = self.balance - v
end
生成對象
對象其實也是一個 Table
, 只不過這個 Table
并不是空的,而是基于一個原型產生的。基于原型的 Table
就是利用上面的 MetaTable 來實現的。同樣基于上面的代碼實現一個產生基于 Account
原型的對象的方法new
:
function Account:new(o)
if o == nil then
o = {}
end
setmetatable(o, self) -- 綁定原型
self.__index = self --索引原型,方便使用點號
return o
end
這個方法的關鍵就是在對象的 MetaTable 中添加了原型 Account
。這樣當我們要訪問對象 o
這個中的某個未定義的字段時,Lua 就會去查找它的 MetaTable 中是否有這個字段,由于我們綁定了原型,這樣就能找到原型中的這個字段。比如,我們并沒有為 o
定義 withdraw
方法,所以當我們訪問 o
的 withdraw 字段時,就會發現 o
本身的 Table
并沒有這個字段,然后去查找它的 MetaTable,這樣就能訪問到原型,也就是 Account
的 withdraw
的實現。
派生
由原有的原型派生出新的原型在 Lua 中實現起來也不難,因為原型也是 Table
, 我們只要為這個 Table
添加新的字段,或者為原有字段定義新的內容就行了,例如:
SpecialAccount = Account:new{limit = 100}
-- 重新定義 withdraw 方法
function SpecialAccount:withdraw(v)
if v < self.limit then
self.balance = self.balance - v
end
end
在上面的代碼中就從 Account
原型中派生出了一個新的原型,其中添加了一個新的limit
成員變量和重新定義了withdraw
成員方法。