lua中神奇的table
最近在尝试配置 awesome WM,因此粗略地学习了一下 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 函数来进行遍历。
for k,v in pairs(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 = {}
for i=0,5 do -- 注意,这里故意写成了i从0开始
a[i] = 0
end
当将 table 当成数组来用时,可以通过 长度操作符 # 来获取数组的长度
print(#a)
结果为
5
你会发现, lua 认为 数组 a 中只有5个元素,到底是哪5个元素呢?我们可以使用使用 ipairs 对数组进行遍历:
for i,v in ipairs(a) do
print(i,v)
end
结果为
1 0
2 0
3 0
4 0
5 0
从结果中你会发现 a 的0号索引并不认为是数组中的一个元素,从而也验证了 lua 中的数组是从 1开始索引的
另外,将table当成数组来用时,一定要注意索引不连贯的情况,这种情况下 # 计算长度时会变得很诡异
a = {}
for i=1,5 do
a[i] = 0
end
a[8] = 0 -- 虽然索引不连贯,但长度是以最大索引为准
print(#a)
a[100] = 0 -- 索引不连贯,而且长度不再以最大索引为准了
print(#a)
结果为:
8
8
而使用 ipairs 对数组进行遍历时,只会从1遍历到索引中断处
for i,v in ipairs(a) do
print(i,v)
end
结果为:
1 0
2 0
3 0
4 0
5 0
环境(命名空间)
lua将所有的全局变量/局部变量保存在一个常规table中,这个table一般被称为全局或者某个函数(闭包)的环境。
为了方便,lua在创建最初的全局环境时,使用全局变量 _G 来引用这个全局环境。因此,在未手工设置环境的情况下,可以使用 _G[varname] 来存取全局变量的值.
for k,v in pairs(_G) do
print(k,"->",v)
end
require -> function: 0x1ea4e70
_VERSION -> Lua 5.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
从lua 5.2开始,可以通过修改 _ENV 这个值(lua5.1中的setfenv从5.2开始被废除)来设置某个函数的环境,从而让这个函数中的执行语句在一个新的环境中查找全局变量的值。
a=1 -- 全局变量中a=1
local env={a=10,print=_G.print} -- 新环境中a=10,并且确保能访问到全局的print函数
function f1()
local _ENV=env
print("in f1:a=",a)
a=a*10 -- 修改的是新环境中的a值
end
f1()
print("globally:a=",a)
print("env.a=",env.a)
in f1:a= 10
globally:a= 1
env.a= 100
另外,新创建的闭包都继承了创建它的函数的环境
module
lua 中的模块也是通过返回一个table来供模块使用者来使用的。 这个 table中包含的是模块中所导出的所有东西,包括函数和常量。
定义module的一般模板为
module(模块名, package.seeall)
其中 module(模块名) 的作用类似于
local modname = 模块名
local M = {} -- M即为存放模块所有函数及常数的table
_G[modname] = M
package.loaded[modname] = M
setmetatable(M,{__index=_G}) -- package.seeall可以使全局环境_G对当前环境可见
local _ENV = M -- 设置当前的运行环境为 M,这样后续所有代码都不需要限定模块名了,所定义的所有函数自动变成M的成员<函数定义以及常量定义>
return M -- module函数会帮你返回module table,而无需手工返回
对象
lua 中之所以可以把table当成对象来用是因为:
- 函数在 lua 中是一类值,你可以直接存取table中的函数值。 这使得一个table既可以有自己的状态,也可以有自己的行为:
Account = {balance = 0}
function Account.withdraw(v)
Account.balance = Account.balance - v
end
- lua 支持闭包,这个特性可以用来模拟对象的私有成员变量
function new_account(b)
local balance = b
return {withdraw = function (v) balance = balance -v end,
get_balance = function () return balance end
}
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}
function Account.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 到地一个参数位置。 即 function object:method(v) end 等价于 function object.method(self,v) end, object:method(v) 等价于 object.method(object,v)
类
当涉及到类和继承时,就要用到元表和元方法了。事实上,对于 lua 来说,对象和类并不存在一个严格的划分。
当一个对象被另一个table的 __index 元方法所引用时,table就能引用该对象中所定义的方法,因此也就可以理解为对象变成了table的类。
类定义的一般模板为:
function 类名:new(o)
o = o or {}
setmetatable(o,{__index = self})
return o
end
或者
function 类名:new(o)
o = o or {}
setmetatable(o,self)
self.__index = self
return o
end
相比之下,第二种写法可以多省略一个table
另外有一点我觉得有必要说明的就是 lua 中的元方法是在元表中定义的,而不是对象本身定义的,这一点跟其他面向对象的语言比较不同。
欢迎使用Markdown编辑器写博客
本Markdown编辑器使用StackEdit修改而来,用它写博客,将会带来全新的体验哦:
- Markdown和扩展Markdown简洁的语法
- 代码块高亮
- 图片链接和图片上传
- LaTex数学公式
- UML序列图和流程图
- 离线写博客
- 导入导出Markdown文件
- 丰富的快捷键
快捷键
- 加粗
Ctrl + B
- 斜体
Ctrl + I
- 引用
Ctrl + Q
- 插入链接
Ctrl + L
- 插入代码
Ctrl + K
- 插入图片
Ctrl + G
- 提升标题
Ctrl + H
- 有序列表
Ctrl + O
- 无序列表
Ctrl + U
- 横线
Ctrl + R
- 撤销
Ctrl + Z
- 重做
Ctrl + Y
Markdown及扩展
Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成格式丰富的HTML页面。 —— [ 维基百科 ]
使用简单的符号标识不同的标题,将某些文字标记为粗体或者斜体,创建一个链接等,详细语法参考帮助?。
本编辑器支持 Markdown Extra , 扩展了很多好用的功能。具体请参考Github.
表格
Markdown Extra 表格语法:
项目 | 价格 |
---|---|
Computer | $1600 |
Phone | $12 |
Pipe | $1 |
可以使用冒号来定义对齐方式:
项目 | 价格 | 数量 |
---|---|---|
Computer | 1600 元 | 5 |
Phone | 12 元 | 12 |
Pipe | 1 元 | 234 |
定义列表
-
Markdown Extra 定义列表语法:
项目1
项目2
- 定义 A
- 定义 B 项目3
- 定义 C
-
定义 D
定义D内容
代码块
代码块语法遵循标准markdown代码,例如:
@requires_authorization
def somefunc(param1='', param2=0):
'''A docstring'''
if param1 > param2: # interesting
print 'Greater'
return (param2 - param1 + 1) or None
class SomeClass:
pass
>>> message = '''interpreter
... prompt'''
脚注
生成一个脚注1.
目录
用 [TOC]
来生成目录:
数学公式
使用MathJax渲染LaTex 数学公式,详见math.stackexchange.com.
- 行内公式,数学公式为: Γ(n)=(n−1)!∀n∈N Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N 。
- 块级公式:
更多LaTex语法请参考 这儿.
UML 图:
可以渲染序列图:
或者流程图:
离线写博客
即使用户在没有网络的情况下,也可以通过本编辑器离线写博客(直接在曾经使用过的浏览器中输入write.blog.youkuaiyun.com/mdeditor即可。Markdown编辑器使用浏览器离线存储将内容保存在本地。
用户写博客的过程中,内容实时保存在浏览器缓存中,在用户关闭浏览器或者其它异常情况下,内容不会丢失。用户再次打开浏览器时,会显示上次用户正在编辑的没有发表的内容。
博客发表后,本地缓存将被删除。
用户可以选择 把正在写的博客保存到服务器草稿箱,即使换浏览器或者清除缓存,内容也不会丢失。
注意:虽然浏览器存储大部分时候都比较可靠,但为了您的数据安全,在联网后,请务必及时发表或者保存到服务器草稿箱。
浏览器兼容
- 目前,本编辑器对Chrome浏览器支持最为完整。建议大家使用较新版本的Chrome。
- IE9以下不支持
- IE9,10,11存在以下问题
- 不支持离线功能
- IE9不支持文件导入导出
- IE10不支持拖拽文件导入
- 这里是 脚注 的 内容. ↩