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有微妙的相似感.