lua中神奇的table

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

可以使用冒号来定义对齐方式:

项目价格数量
Computer1600 元5
Phone12 元12
Pipe1 元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)=(n1)!nN Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N
  • 块级公式:

x=b±b24ac2a x = − b ± b 2 − 4 a c 2 a

更多LaTex语法请参考 这儿.

UML 图:

可以渲染序列图:

Created with Raphaël 2.1.2 张三 张三 李四 李四 嘿,小四儿, 写博客了没? 李四愣了一下,说: 忙得吐血,哪有时间写。

或者流程图:

Created with Raphaël 2.1.2 开始 我的操作 确认? 结束 yes no
  • 关于 序列图 语法,参考 这儿,
  • 关于 流程图 语法,参考 这儿.

离线写博客

即使用户在没有网络的情况下,也可以通过本编辑器离线写博客(直接在曾经使用过的浏览器中输入write.blog.youkuaiyun.com/mdeditor即可。Markdown编辑器使用浏览器离线存储将内容保存在本地。

用户写博客的过程中,内容实时保存在浏览器缓存中,在用户关闭浏览器或者其它异常情况下,内容不会丢失。用户再次打开浏览器时,会显示上次用户正在编辑的没有发表的内容。

博客发表后,本地缓存将被删除。 

用户可以选择 把正在写的博客保存到服务器草稿箱,即使换浏览器或者清除缓存,内容也不会丢失。

注意:虽然浏览器存储大部分时候都比较可靠,但为了您的数据安全,在联网后,请务必及时发表或者保存到服务器草稿箱

浏览器兼容

  1. 目前,本编辑器对Chrome浏览器支持最为完整。建议大家使用较新版本的Chrome。
  2. IE9以下不支持
  3. IE9,10,11存在以下问题
    1. 不支持离线功能
    2. IE9不支持文件导入导出
    3. IE10不支持拖拽文件导入


  1. 这里是 脚注内容.
<think>好的,我现在需要帮助用户了解如何遍历Lua中的table。用户之前已经询问过如何构造嵌套的table,现在他们可能想进一步学习如何访问和操作这些数据结构。首先,我应该回顾用户之前的提问,确保回答的一致性和连贯性。 用户的问题是关于遍历Lua中的table。我需要考虑不同的遍历情况,比如数组部分、键值对、以及混合类型的tableLuatable非常灵活,但这也可能导致用户在遍历时遇到困惑,尤其是索引从1开始和隐式键的问题。 接下来,我需要明确遍历的几种常用方法:ipairs、pairs、自定义遍历。用户可能对这三种方法的区别不太清楚,所以需要详细解释每种方法的适用场景和注意事项。例如,ipairs只遍历连续数字索引的数组部分,而pairs可以遍历所有键值对,但顺序不保证。另外,自定义循环可能适合处理不连续的索引或者需要特定顺序的情况。 还要注意用户可能遇到的常见错误,比如在非连续数组中使用ipairs导致提前终止,或者误解键的类型(比如字符串键和数字键的区别)。需要给出错误示例和解决方法,帮助用户避免陷阱。 此外,用户之前的例子中提到了嵌套的table结构,所以在回答中可以提到如何遍历嵌套的table,比如多层循环或者递归遍历,但可能要根据用户当前的问题范围来决定是否深入讨论。如果用户的问题只是基础遍历,可能不需要涉及太复杂的嵌套情况,但可以适当提一下作为扩展。 另外,用户可能关心性能问题,尤其是在处理大型table时,不同的遍历方式是否有性能差异。需要简单说明ipairs和pairs的效率,以及如何优化遍历过程,比如预分配数组大小或者避免在循环中修改table结构。 最后,总结部分要简明扼要,强调选择正确的遍历方法的重要性,以及在不同情况下如何选择合适的工具。确保回答结构清晰,步骤明确,帮助用户逐步理解和应用不同的遍历方法。</think>以下是Lua中遍历table的详细方法及适用场景: --- ### **一、基础遍历方法** #### 1. **`ipairs`遍历数组部分** - **特点**:按连续数字索引遍历(1,2,3...),遇到`nil`时停止 - **适用场景**:处理纯数组结构的table ```lua local arr = { "a", "b", "c" } for i, v in ipairs(arr) do print(i, v) -- 输出:1 a → 2 b → 3 c end ``` #### 2. **`pairs`遍历所有元素** - **特点**:遍历所有键值对(包括非数字键),顺序不固定 - **适用场景**:处理混合键类型或哈希表 ```lua local t = { name = "Lua", [3] = "three", "first" } for k, v in pairs(t) do print(k, v) -- 可能输出:1 first → 3 three → name Lua end ``` #### 3. **自定义数字索引遍历** - **特点**:精确控制索引范围 - **适用场景**:非连续数字索引或需要倒序遍历 ```lua local t = { [1] = "A", [3] = "C" } for i = 1, 5 do if t[i] then print(t[i]) -- 输出A → C → (其他索引为nil不输出) end end ``` --- ### **二、特殊场景处理** #### 1. **跳过`nil`的数组遍历 ```lua local sparse = { [1]="a", [3]="c" } for i=1, #sparse do if sparse[i] then print(sparse[i]) -- 输出a → c(跳过索引2的nil) end end ``` #### 2. **遍历嵌套table** ```lua local matrix = { {1,2,3}, {4,5,6} } for row_index, row in ipairs(matrix) do for col_index, num in ipairs(row) do print(row_index, col_index, num) end end -- 输出: -- 1 1 1 → 1 2 2 → 1 3 3 -- 2 1 4 → 2 2 5 → 2 3 6 ``` --- ### **三、关键注意事项** #### 1. **索引起点差异** - Lua数组索引默认从1开始(不同于其他语言的0) ```lua local t = {"a","b","c"} print(t[0]) -- 输出nil print(t[1]) -- 输出a ``` #### 2. **长度运算符`#`的陷阱** - `#t`返回最大的连续数字索引值 ```lua local t = { [1]="a", [2]="b", [4]="d" } print(#t) -- 输出2(因为索引3缺失) ``` #### 3. **遍历时修改table** - 在`pairs/ipairs`遍历过程中删除元素可能导致意外结果 ```lua local t = {1,2,3,4} for k,v in ipairs(t) do if v == 2 then table.remove(t, k) -- 删除元素后后续遍历会跳过索引3 end end print(table.concat(t, ",")) -- 输出"1,3,4" ``` --- ### **四、性能优化建议** 1. **优先使用`ipairs`**:比`pairs`快约30%(LuaJIT环境下) 2. **避免在循环中重复计算长度** ```lua -- 优化前 for i=1, #t do ... end -- 优化后 local len = #t for i=1, len do ... end ``` 3. **预分配数组空间**:减少扩容时的性能损耗 ```lua local arr = {} for i=1,1000 do arr[i] = 0 -- 优于直接插入 end ``` --- ### **五、遍历方式对比表** | 方法 | 遍历范围 | 顺序保证 | 性能 | 典型用例 | |---------------|--------------|----------|------|--------------------| | `ipairs()` | 连续数字索引 | 顺序保证 | 高 | 处理数组数据 | | `pairs()` | 所有键值对 | 不保证 | 中 | 处理混合类型table | | 自定义数字循环 | 指定索引范围 | 完全控制 | 高 | 非连续索引或筛选遍历 | --- ### **总结** 选择遍历方式时需考虑: 1. 数据结构特性(纯数组/混合键类型) 2. 是否需要保证顺序 3. 性能需求 4. 是否需要处理嵌套结构 对于JSON-like的复杂数据,建议优先使用`pairs`;处理游戏中的数组类数据(如角色属性列表)时,`ipairs`是更优选择。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值