深入谈谈lua中神奇的table
前言
最近在尝试配置awesomeWM,因此粗略地学习了一下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函数来进行遍历。
fork,vinpairs(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={} fori=0,5do--注意,这里故意写成了i从0开始 a[i]=0 end
当将table当成数组来用时,可以通过长度操作符#来获取数组的长度
print(#a)
结果为
5
你会发现,lua认为数组a中只有5个元素,到底是哪5个元素呢?我们可以使用使用ipairs对数组进行遍历:
fori,vinipairs(a)do print(i,v) end
结果为
10
20
30
40
50
从结果中你会发现a的0号索引并不认为是数组中的一个元素,从而也验证了lua中的数组是从1开始索引的
另外,将table当成数组来用时,一定要注意索引不连贯的情况,这种情况下#计算长度时会变得很诡异
a={} fori=1,5do a[i]=0 end a[8]=0--虽然索引不连贯,但长度是以最大索引为准 print(#a) a[100]=0--索引不连贯,而且长度不再以最大索引为准了 print(#a)
结果为:
8
8
而使用ipairs对数组进行遍历时,只会从1遍历到索引中断处
fori,vinipairs(a)do print(i,v) end
结果为:
10
20
30
40
50
环境(命名空间)
lua将所有的全局变量/局部变量保存在一个常规table中,这个table一般被称为全局或者某个函数(闭包)的环境。
为了方便,lua在创建最初的全局环境时,使用全局变量_G来引用这个全局环境。因此,在未手工设置环境的情况下,可以使用_G[varname]来存取全局变量的值.
fork,vinpairs(_G)do print(k,"->",v) end
rawequal -> function:0x41c2a0
require-> function:0x1ea4e70
_VERSION -> Lua5.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
从lua5.2开始,可以通过修改_ENV这个值(lua5.1中的setfenv从5.2开始被废除)来设置某个函数的环境,从而让这个函数中的执行语句在一个新的环境中查找全局变量的值。
a=1--全局变量中a=1 localenv={a=10,print=_G.print}--新环境中a=10,并且确保能访问到全局的print函数 functionf1() local_ENV=env print("inf1:a=",a) a=a*10--修改的是新环境中的a值 end f1() print("globally:a=",a) print("env.a=",env.a)
inf1:a=10 globally:a=1 env.a=100
另外,新创建的闭包都继承了创建它的函数的环境
module
lua中的模块也是通过返回一个table来供模块使用者来使用的。这个table中包含的是模块中所导出的所有东西,包括函数和常量。
定义module的一般模板为
module(模块名,package.seeall)
其中module(模块名)的作用类似于
localmodname=模块名
localM={} --M即为存放模块所有函数及常数的table
_G[modname]=M
package.loaded[modname]=M
setmetatable(M,{__index=_G}) --package.seeall可以使全局环境_G对当前环境可见
local_ENV=M --设置当前的运行环境为M,这样后续所有代码都不需要限定模块名了,所定义的所有函数自动变成M的成员
<函数定义以及常量定义>returnM --module函数会帮你返回moduletable,而无需手工返回
对象
lua中之所以可以把table当成对象来用是因为:
函数在lua中是一类值,你可以直接存取table中的函数值。这使得一个table既可以有自己的状态,也可以有自己的行为:
Account={balance=0} functionAccount.withdraw(v) Account.balance=Account.balance-v end
lua支持闭包,这个特性可以用来模拟对象的私有成员变量
functionnew_account(b) localbalance=b return{withdraw=function(v)balance=balance-vend, get_balance=function()returnbalanceend } 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} functionAccount.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到地一个参数位置。即functionobject:method(v)end等价于functionobject.method(self,v)end,object:method(v)等价于object.method(object,v)
类
当涉及到类和继承时,就要用到元表和元方法了。事实上,对于lua来说,对象和类并不存在一个严格的划分。
当一个对象被另一个table的__index元方法所引用时,table就能引用该对象中所定义的方法,因此也就可以理解为对象变成了table的类。
类定义的一般模板为:
function类名:new(o) o=oor{} setmetatable(o,{__index=self}) returno end
或者
function类名:new(o) o=oor{} setmetatable(o,self) self.__index=self returno end
相比之下,第二种写法可以多省略一个table
另外有一点我觉得有必要说明的就是lua中的元方法是在元表中定义的,而不是对象本身定义的,这一点跟其他面向对象的语言比较不同。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。