深入谈谈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

rawequal  ->  function: 0x41c2a0
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 中的元方法是在元表中定义的,而不是对象本身定义的,这一点跟其他面向对象的语言比较不同。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Lua table中安全移除元素的方法

    在Lua中,table如何安全的移除元素这点挺重要,因为如果不小心,会没有正确的移除,造成内存泄漏. 引子 比如有些朋友常常这么做,大家看有啥问题 将test表中的偶数移除掉 复制代码 代码如下: local test = { 2, 3, 4, 8, 9, 100, 20, 13, 15, 7, 11} for i, v in ipairs( test ) do     if v % 2 == 0 then         table.remove(test, i)     end end fo

  • Lua中设置table为只读属性的方法详解

    项目中部分只读表易被人误改写,故决定在非线上环境里对这些表附加只读属性,方便在出现误改写的时候抛出lua错误,最终版代码如下: --[[------------------------------------------------------------------------------ -** 设置table只读 出现改写会抛出lua error -- 用法 local cfg_proxy = read_only(cfg) retur cfg_proxy -- 增加了防重置设置read_o

  • Lua中获取table长度问题探讨

    又有同事在lua的table长度问题上犯错了,我们一起来看看吧~~~ 看以下代码: 复制代码 代码如下: local tblTest1 =  {      1,      2,      3  }    print(table.getn(tblTest1)) 这段代码输出的结果是3,这个大家都知道,是吧.不管最后那个3后面有没有加逗号,结果都是3. 再看下面的代码: 复制代码 代码如下: local tblTest2 =  {      1,      a = 2,      3,  }   

  • Lua面向对象编程之基础结构table简例

    面向对象编程 (Object Oriented Programming,OOP)是一种非常流行的计算机编程架构. Lua中最基本的结构是table,所以需要用table来描述对象的属性. Lua中的function可以用来表示方法.那么Lua中的类可以通过table + function模拟出来. 简例: -- Czhenya Lua 面向对象 -- 对于一个对象来说, 属性 方法 --[[ 两种定义函数的方式 person.eat = function() print(person.name.

  • Lua中table里内嵌table的例子

    废话不多少,看代码: 复制代码 代码如下: local temp_insert_table_Data = {      x = "test3",        y = "test4",        z = "test5" ,  }    local temp_insert_table = {      x = 1,      y = 2,      z = 3,      {   a = -1, a1 = 1},      {   b = -2

  • Lua编程示例(一):select、debug、可变参数、table操作、error

    function test_print(...) for i=1,select("#",...) do print(i,select(i,...)) end end test_print(11,12,13,14) print() print(debug.traceback()) print() function test(...) for i=1,arg.n do print(i.."\t"..arg[i]) end end test("a",2

  • Lua中获取table长度的方法

    官方文档是这么描述#的: 取长度操作符写作一元操作 #. 字符串的长度是它的字节数(就是以一个字符一个字节计算的字符串长度). table t 的长度被定义成一个整数下标 n . 它满足 t[n] 不是 nil 而 t[n+1] 为 nil: 此外,如果 t[1] 为 nil ,n 就可能是零. 对于常规的数组,里面从 1 到 n 放着一些非空的值的时候, 它的长度就精确的为 n,即最后一个值的下标. 如果数组有一个"空洞" (就是说,nil 值被夹在非空值之间), 那么 #t 可能是

  • Lua的table库函数insert、remove、concat、sort详细介绍

    函数列表: table.insert(table,[ pos,] value) table.remove(table[, pos]) table.concat(table[, sep[, i[, j]]]) table.sort(table[, comp]) 1. insert 和 remove 只能用于数组元素的插入和移出, 进行插入和移出时,会将后面的元素对齐起来. 所以在 for 循环中进行 insert 和 remove 的时候要注意插入和移除时是否漏掉了某些项:   复制代码 代码如下

  • 举例讲解Lua中的Table数据结构

    文中-- 两个横线开始单行的注释,--[[加上两个[和]表示多行的注释--]]. 复制代码 代码如下: -- Table = Lua唯一的数据结构; --         它们是关联数组. -- 类似于PHP的数组或者js的对象, -- 它们是哈希查找表(dict),也可以按list去使用. 复制代码 代码如下: -- 按字典/map的方式使用Table: -- Dict的迭代默认使用string类型的key: t = {key1 = 'value1', key2 = false} 复制代码 代

  • Lua中table的遍历详解

    当我在工作中使用lua进行开发时,发现在lua中有4种方式遍历一个table,当然,从本质上来说其实都一样,只是形式不同,这四种方式分别是: 复制代码 代码如下: for key, value in pairs(tbtest) do      XXX  end   for key, value in ipairs(tbtest) do      XXX  end   for i=1, #(tbtest) do      XXX  end   for i=1, table.maxn(tbtest)

  • C++遍历Lua table的方法实例

    Lua table数据如下: 复制代码 代码如下: --$ cat test.lua lua文件 user = {         ["name"] = "zhangsan",         ["age"] = "22",         ["friend"] = {                 [1] = {                     ["name"] = &quo

  • Lua中释放table占用内存的方法

    table的大小是动态变化的.看如下代码: 复制代码 代码如下: tb = {1,2,3} --数组大小4,hash表大小1(不管hash表有没有存数据,它的大小最小为1)    tb[5] = 5  tb[100] = 100 --数组大小4,hash表大小2 因为后插入的两个数的key过于离散,所以它们被保存到hash表中.最终tb的大小为4+2=6. 这时我们对table进行删除操作: 复制代码 代码如下: tb[1] = nil  tb[2] = nil  tb[3] = nil  tb

  • Lua Table转C# Dictionary的方法示例

    table特性 table是一个"关联数组",数组的索引可以是数字或者是字符串,所有索引值都需要用 "["和"]" 括起来:如果是字符串,还可以去掉引号和中括号: 即如果没有[]括起,则认为是字符串索引 table 的默认初始索引一般以 1 开始,如果不写索引,则索引就会被认为是数字,并按顺序自动从1往后编: table 的变量只是一个地址引用,对 table 的操作不会产生数据影响 table 不会固定长度大小,有新数据插入时长度会自动增长 t

随机推荐