LUA基礎: TABLE, ARRAY, NAMESPACE, LUA的物件導向

工程師WWW發表於2013-12-02

一、table的宣告及賦值

table是Lua中的hashmap(包括其實現方式也是). 由於其包含所有陣列的功能, 所以可以認為table是擴充套件了的陣列. Lua沒有再額外提供單獨的陣列型別.

用以下的語句宣告一個空的table:

newTable = {}

可以用以下的方式向table中新增值:

newTable[2] = "14"

可以使用字串形式的key值, 但必須在字串前後新增雙引號("").

newTable["time"] = "April 14"

也可以在首次宣告table的同時進行賦值, 參考下面的語句:

newTable = {
  [key1] = value1,
  [key2] = value2,
  ...
}

陣列(array)在Lua中被當作table的一個特例, 即: 以1開始的連續整數作為key的table.

所以在宣告時不需再指定key.

newArray = {
  value1,
  value2,
  ...
}

一個table如果包含key值為1的元素, 則該元素, 以及其後以2開始的連續的整數為key值的所有元素構成的table, 稱作該table的陣列部分. 一部分table的函式只會對table的陣列部分產生作用.

和其他語言的陣列一樣, 可以通過下標獲取特定的元素, 不過下標是從1而非0開始的:

> firstTable = {"alpha", "beta", "gamma"}
> print(firstTable[1])
alpha

對比: 可以用下面的方式獲取字串形式的key所對應的值:

> print(newTable.time)
April 14

可以通過#運算子獲取陣列的長度.

> print(#firstTable)
3

#運算子不會處理table的非陣列部分.

> secondTable = {
>> "alpha",
>> "beta",
>> ["one"] = "uno",
>> ["two"] = "dos",
>> "gamma",
>> }
> print(#secondTable)
3

附註: 一個table的陣列部分可以以內建的table.sort()函式進行升序排列:

> newTable = {"alpha", "gamma", "beta"}
> table.sort(newTable)
> for i = 1, #newTable do
>> print(i, newTable[i])
>> end
1, alpha
2, beta
3, gamma

關於刪除:
在任何的table中, 都可以用下面的語句清空一個key所對應的值:

newTable["time"] = nil

以下語句只支援array形式的table:

value = table.remove(newTable)
value = table.remove(newTable, 2)

在刪除的同時將返回被刪除的key原本的值.


二、使用table建立namespace

table的一個元素的值可以是一段程式碼, 此時這個元素相當於一個函式(或方法). 換言之, 可以利用table來產生一個namespace.

回顧: 字串形式的key所對應的值, 可以通過兩種方式來獲取:

> print(newTable.time)
April 14
> print(newTable["time"])
April 14

類似的, 之前所看到的table.insert(), table.remove(), table.sort()三個函式也是table表中所儲存的資料, 換言之它們等價於

table["insert"]()
table["remove"]()
table["sort"]()

具體建立namespace並匯入函式的方式可參考下列程式碼:

> util = {}
> function convert(celsius)
>> return ((celsius * 1.8) + 32)
>> end
> util.convert = convert
> print(convert(0))
32
> print(util.convert(0))
32

或者, 更直接地:

> function util.convert(celsius)
>> return ((celsius * 1.8) + 32)
>> end
> print(util.convert(0))
32


三、通過table實現OOP

通過上述的table功能, 可以在Lua中方便地實現物件導向程式設計.

首先, 用一般的table宣告方式先宣告"private data"

counter = {
  count = 0
}

接下來再追加其中的方法:

function counter.get(self)
  return self.count
end

function counter.inc(self)
  self.count = self.count + 1
end

這樣就構成了一個計數器物體, 有一個計數用的變數(count)以及兩個計數相關的方法, 可以這樣使用:

> print(counter.get(counter))
0
> counter.inc(counter)
> print(counter.get(counter))
1

可以再產生一個相同型別的物件, 需要重複書寫一部分程式碼; 函式可以不用重新定義.

> counter2 = {
 count = 15,
 get = counter.get,
 inc = counter.inc,
}
> print(counter2.get(counter2))
15

這種定義方式的好處是免除了在其他OOP語言當中經常浪費程式編寫人員精力的疑問, 例如令人頭大的訪問許可權(private, public, protected之類), 以及 "我用一個=運算子copy了一個方法, 我到底是copy了方法的內容、指標, 或者只是把新方法指向了當前的方法" 等等疑問.

上述方式的一個麻煩是, 在呼叫table自身變數(例如count)的時候, 必須再將table名輸入一次(例如counter.get(counter)輸入了兩次counter). 這樣的方式, 在疏忽下可能產生混亂(如: counter2.get(counter)). 一種可行的改進方法是使用":"代替"."來呼叫object method.

在Lua中, 以下的兩種定義函式的方式是等價的:

function tbl:MyMethod() end 
function tbl.MyMethod(self) end

並且, 這裡的self就是擁有此method的table. 看下面的程式碼:

counter = {
 count = 0 
}

function counter:get()
 return self.count 
end

function counter:inc()
 self.count = self.count + 1 
end

程式碼被進一步地優化了.

利用:的定義方式, 可以把之前的OOP語句 改造得和其他OOP語言更加接近.

do
 local function get(self)
  return self.count
 end

 local function inc(self)
  self.count = self.count + 1
 end

 function new_counter(value) -- 注意這個function不是local的. 這是個建構器
  if type(value) ~= "number" then
   value = 0
  end

  local obj = {
   count = value,
   get = get,
   inc = inc,
  }
  return obj
 end
end

> counter = new_counter()
> print(counter:get())
0
> counter2 = new_counter(15)
> print(counter2:get())
15
> counter:inc()
> print(counter:get())
1
> print(counter2:get())
15

這樣的程式碼看起來和C++/Java有微妙的相似感.

相關文章