說點什么
由于
Luatable的特殊構(gòu)造,使用純Lua實現(xiàn) 純數(shù)組 是很困難的——table是Array和HashMap的混合體, 二者合二為一,你中有我,我中有你,水乳交融,難以切分。因此,Array很容易被污染而喪失Array的特性,HashMap亦然。
但table還有一個奇妙的特性——元表,借助 元表 的特殊運用,我們可以實現(xiàn)一個比較嚴格的數(shù)組Array。
基本要求
- 不可以存在非整數(shù)的索引;
- 可通過索引直接訪問元素;
- 傳入元素可直接創(chuàng)建一個數(shù)組,不傳默認為空數(shù)組;
- 不允許數(shù)組索引越界;
- 提供插入、移除、修改數(shù)組元素等基本操作數(shù)組的方法 (有需要的話,可以按需求增加數(shù)組操作的方法,這里主要提供的是實現(xiàn)機制和思想);
- 數(shù)組索引自動管理。
硬性約束
- 不允許直接通過索引對元素設值為nil,也就是如下形式的賦值是不允許的:
arr[1] = nil;
這是因為對索引元素設置nil的話,數(shù)組的索引會被破壞; - 為了秉承
Lua的習慣,第一個元素的索引為1。
實現(xiàn)
還是 table
首先,我們創(chuàng)建一個數(shù)組構(gòu)造函數(shù):
function Array(...)
local __array__ = {...}
-- do something to construct an array
end
通過 Array() 來構(gòu)造一個數(shù)組,其中 ... 是傳入的元素參數(shù),__array__ 用來存儲數(shù)組元素。
嗯哼,目前看來,__array__ 可不還是一個table嗎,其實還是什么都沒有做呀。
別急,接下來,重頭戲就來了。
變異的 table
為了構(gòu)造一個嚴格的數(shù)組,我們首要的問題就是保證 __array__ 存儲的都是整數(shù)的索引,這就要求我們就不能直接對 __array__ 進行操作,而要對它進行特殊保護——對外隱藏:
function Array(...)
local new_array = {}
local __array__ = {...}
local mt = {__index = new_array}
setmetatable(new_array, mt)
return new_array
end
為了實現(xiàn)隱藏的目的,我們增加了一個新的變量 new_array,讓其元表指向 __array__,這樣就可以直接通過索引訪問數(shù)組元素,而又達到了隱藏真實數(shù)組的目的。
但這樣還有兩個嚴重的安全隱患:
- 通過
getmetatable(new_array).__index依然可以獲得真實的數(shù)組數(shù)據(jù); - 最終返回的構(gòu)造數(shù)組將是
new_array,那么new_array必須具備數(shù)組的基本特性。
針對第一個問題,我們可以將__index 改為函數(shù),實現(xiàn)徹底隱藏 __array__的目的:
function Array(...)
local new_array = {}
local __array__ = {...}
local mt = {
__index = function(t, k)
return __array__[k]
end
}
setmetatable(new_array, mt)
return new_array
end
現(xiàn)在我們來看第二個問題,new_array 將作為外交官,完成對真實數(shù)組 __array__ 的內(nèi)部操作,那么 new_array 就必須看起來像真實的數(shù)組。為了保證 new_array 的純凈,我們必須再次改造它的元表。
function Array(...)
local new_array = {}
local __array__ = {...}
local mt = {
__index = function(t, k)
return __array__[k]
end,
__newindex = function(t, k, v)
if nil == __array__[k] then
print(string.format('warning : [%s] index out of range.', tostring(k)))
return
end
if nil == v then
print(string.format('warning : can not remove element by using `nil`.'))
return
end
__array__[k] = v
end
}
setmetatable(new_array, mt)
return new_array
end
我們對元表增加了 __newindex 的屬性,它控制著對 new_array 的內(nèi)部元素的附帶副作用的操作??梢钥闯?,目前我們只允許 new_array 修改 __array__ 已存在的元素的值 (且不允許對直接對元素設值為 nil,原因我們在 硬性約束中說過了),也就是所有與 __array__ 無關(guān)的操作都被過濾了。
到目前為止,new_array 已經(jīng)是一個較為嚴格的 數(shù)組 了,下面就要對這個 數(shù)組 進行擴展了,畢竟,除了能夠?qū)σ汛嬖诘脑剡M行賦值操作,現(xiàn)在的這個 數(shù)組 什么都做不了。
擴展 Array
為了擴展 Array 的操作屬性,必須添加額外的方法對 __array__ 進行操作,我們像構(gòu)造 __array__ 一樣,創(chuàng)建一個變量 __methods__ 用來保存方法列表,同樣,為了能夠訪問到 methods 中的方法,我們必須在元表中的 __index 添加 __methods__ 的訪問途徑 (在 web 中,這個概念是叫路由吧):
function Array(...)
local new_array = {}
local __array__ = {...}
local __methods__ = {}
function __methods__:insert(v, at)
local len = #__array__ + 1
at = type(at) == 'number' and at or len
at = math.min(at, len)
table.insert(__array__, at, v)
end
function __methods__:removeAt(at)
at = type(at) == 'number' and at or #__array__
table.remove(__array__, at)
end
function __methods__:print()
print('---> array content begin <---')
for i, v in ipairs(__array__) do
print(string.format('[%s] => ', i), v)
end
print('---> array content end <---')
end
-- extend methods here
local mt = {
__index = function(t, k)
if __array__[k] then
return __array__[k]
end
if __methods__[k] then
return __methods__[k]
end
end,
__newindex = function(t, k, v)
if nil == __array__[k] then
print(string.format('warning : [%s] index out of range.', tostring(k)))
return
end
if nil == v then
print(string.format('warning : can not remove element by using `nil`.'))
return
end
__array__[k] = v
end
}
setmetatable(new_array, mt)
return new_array
end
這里,為了方便展示,僅寫了增、刪兩種方法,更多的還需要讀者自己補充。我們來做一些 測試:
local arr = Array(1,2,3)
print(arr[1]) -- 1
print(arr[4]) -- nil
arr[1] = 4
arr:print() -- 4,3,2
arr[4] = 'a' -- warning : [4] index out of range.
arr[2] = nil -- warning : can not remove element by using `nil`.
arr:insert('a')
arr:insert('b', 2)
arr:print() -- 4,b,2,3,a
arr:removeAt(1)
arr:print() -- b,2,3,a
好了,現(xiàn)在一個較為嚴格的數(shù)組已經(jīng)完成了,怎么用,就看大家的意愿了。
PS: 借助同樣的機制,我們可以實現(xiàn)一個純 HashMap,懶得動,就不寫教程了吧。
參考:完整代碼