最近在尝试配置 awesome WM,因此粗略地学习了一下 lua 。 在学习过程中,我完全被表表在 lua 中的应用所镇住了。

表在 lua 中真的是无处不在:首先,它可以作为字典和数组来用;此外,它还可以被用于设置闭包环境、模块;甚至可以用来模拟对象和类。

字典

表最基础的作用就是当成字典来用。 它的键可以是除了 nil 之外的任何类型的值。

  1. t={}
  2. t[{}] = "table" -- key 可以是表
  3. t[1] = "int" -- key 可以是整数
  4. t[1.1] = "double" -- key 可以是小数
  5. t[function () end] = "function" -- key 可以是函数
  6. t[true] = "Boolean" -- key 可以是布尔值
  7. t["abc"] = "String" -- key 可以是字符串
  8. t[io.stdout] = "userdata" -- key 可以是userdata
  9. t[coroutine.create(function () end)] = "Thread" -- key可以是thread

当把表当成字典来用时,可以使用 pairs 函数来进行遍历。

  1. for k,v in pairs(t) do
  2. print(k,"->",v)
  3. end

运行结果为:

  1. 1 -> int
  2. 1.1 -> double
  3. thread: 0x220bb08 -> Thread
  4. table: 0x220b670 -> table
  5. abc -> String
  6. file (0x7f34a81ef5c0) -> userdata
  7. function: 0x220b340 -> function
  8. true -> Boolean

从结果中你还可以发现,使用 pairs 进行遍历时的顺序是随机的,事实上相同的语句执行多次得到的结果是不一样的。

表 中的键最常见的两种类型就是整数型和字符串类型。 当键为字符串时,表 可以当成结构体来用。同时形如 t["field"] 这种形式的写法可以简写成 t.field这种形式。

数组

当键为整数时,表 就可以当成数组来用。而且这个数组是一个 索引从 1 开始 、没有固定长度、可以根据需要自动增长的数组。

  1. a = {}
  2. for i=0,5 do -- 注意,这里故意写成了i从0开始
  3. a[i] = 0
  4. end

当将表当成数组来用时,可以通过长度操作符 # 来获取数组的长度:

  1. print(#a)

结果为:

  1. 5

你会发现, lua 认为数组 a 中只有 5 个元素,到底是哪 5 个元素呢?我们可以使用使用 ipairs 对数组进行遍历:

  1. for i,v in ipairs(a) do
  2. print(i,v)
  3. end

结果为:

  1. 1 0
  2. 2 0
  3. 3 0
  4. 4 0
  5. 5 0

从结果中你会发现 a 的 0 号索引并不认为是数组中的一个元素,从而也验证了 lua 中的数组是从 1 开始索引的。

另外,将表当成数组来用时,一定要注意索引不连贯的情况,这种情况下 # 计算长度时会变得很诡异。

  1. a = {}
  2. for i=1,5 do
  3. a[i] = 0
  4. end
  5. a[8] = 0 -- 虽然索引不连贯,但长度是以最大索引为准
  6. print(#a)
  7. a[100] = 0 -- 索引不连贯,而且长度不再以最大索引为准了
  8. print(#a)

结果为:

  1. 8
  2. 8

而使用 ipairs 对数组进行遍历时,只会从 1 遍历到索引中断处。

  1. for i,v in ipairs(a) do
  2. print(i,v)
  3. end

结果为:

  1. 1 0
  2. 2 0
  3. 3 0
  4. 4 0
  5. 5 0

环境(命名空间)

lua 将所有的全局变量/局部变量保存在一个常规表中,这个表一般被称为全局或者某个函数(闭包)的环境。

为了方便,lua 在创建最初的全局环境时,使用全局变量 _G 来引用这个全局环境。因此,在未手工设置环境的情况下,可以使用 -G[varname] 来存取全局变量的值。

  1. for k,v in pairs(_G) do
  2. print(k,"->",v)
  3. end
  1. rawequal -> function: 0x41c2a0
  2. require -> function: 0x1ea4e70
  3. _VERSION -> Lua 5.3
  4. debug -> table: 0x1ea8ad0
  5. string -> table: 0x1ea74b0
  6. xpcall -> function: 0x41c720
  7. select -> function: 0x41bea0
  8. package -> table: 0x1ea4820
  9. assert -> function: 0x41cc50
  10. pcall -> function: 0x41cd10
  11. next -> function: 0x41c450
  12. tostring -> function: 0x41be70
  13. _G -> table: 0x1ea2b80
  14. coroutine -> table: 0x1ea4ee0
  15. unpack -> function: 0x424fa0
  16. loadstring -> function: 0x41ca00
  17. setmetatable -> function: 0x41c7e0
  18. rawlen -> function: 0x41c250
  19. bit32 -> table: 0x1ea8fc0
  20. utf8 -> table: 0x1ea8650
  21. math -> table: 0x1ea7770
  22. collectgarbage -> function: 0x41c650
  23. rawset -> function: 0x41c1b0
  24. os -> table: 0x1ea6840
  25. pairs -> function: 0x41c950
  26. arg -> table: 0x1ea9450
  27. table -> table: 0x1ea5130
  28. tonumber -> function: 0x41bf40
  29. io -> table: 0x1ea5430
  30. loadfile -> function: 0x41cb10
  31. error -> function: 0x41c5c0
  32. load -> function: 0x41ca00
  33. print -> function: 0x41c2e0
  34. dofile -> function: 0x41cbd0
  35. rawget -> function: 0x41c200
  36. type -> function: 0x41be10
  37. getmetatable -> function: 0x41cb80
  38. module -> function: 0x1ea4e00
  39. ipairs -> function: 0x41c970

从 lua 5.2 开始,可以通过修改 _ENV 这个值(lua 5.1 中的 setfenv 从 5.2 开始被废除)来设置某个函数的环境,从而让这个函数中的执行语句在一个新的环境中查找全局变量的值。

  1. a=1 -- 全局变量中a=1
  2. local env={a=10,print=_G.print} -- 新环境中a=10,并且确保能访问到全局的print函数
  3. function f1()
  4. local _ENV=env
  5. print("in f1:a=",a)
  6. a=a*10 -- 修改的是新环境中的a值
  7. end
  8. f1()
  9. print("globally:a=",a)
  10. print("env.a=",env.a)
  1. in f1:a= 10
  2. globally:a= 1
  3. env.a= 100

另外,新创建的闭包都继承了创建它的函数的环境。

模块

lua 中的模块也是通过返回一个表来供模块使用者来使用的。 这个表中包含的是模块中所导出的所有东西,包括函数和常量。

定义模块的一般模板为:

  1. module(模块名, package.seeall)

其中 module(模块名) 的作用类似于:

  1. local modname = 模块名
  2. local M = {} -- M即为存放模块所有函数及常数的table
  3. _G[modname] = M
  4. package.loaded[modname] = M
  5. setmetatable(M,{__index=_G}) -- package.seeall可以使全局环境_G对当前环境可见
  6. local _ENV = M -- 设置当前的运行环境为 M,这样后续所有代码都不需要限定模块名了,所定义的所有函数自动变成M的成员
  7. <函数定义以及常量定义>
  8. return M -- module函数会帮你返回module table,而无需手工返回

对象

lua 中之所以可以把表当成对象来用是因为:

  1. 函数在 lua 中是一类值,你可以直接存取表中的函数值。 这使得一个表既可以有自己的状态,也可以有自己的行为:

    1. Account = {balance = 0}
    2. function Account.withdraw(v)
    3. Account.balance = Account.balance - v
    4. end
  2. lua 支持闭包,这个特性可以用来模拟对象的私有成员变量:

    1. function new_account(b)
    2. local balance = b
    3. return {withdraw = function (v) balance = balance -v end,
    4. get_balance = function () return balance end
    5. }
    6. end
    7. a1 = new_account(1000)
    8. a1.withdraw(10)
    9. print(a1.get_balance())
    1. 990

不过,上面第一种定义对象的方法有一个缺陷,那就是方法与 Account 这个名称绑定死了。 也就是说,这个对象的名称必须为 Accout 否则就会出错。

  1. a = Account
  2. Account = nil
  3. a.withdraw(10) -- 会报错,因为Accout.balance不再存在

为了解决这个问题,我们可以给 withdraw 方法多一个参数用于指向对象本身。

  1. Account = {balance=100}
  2. function Account.withdraw(self,v)
  3. self.balance = self.balance - v
  4. end
  5. a = Account
  6. Account = nil
  7. a.withdraw(a,10) -- 没问题,这个时候 self 指向的是a,因此会去寻找 a.balance
  8. print(a.balance)
  1. 90

不过由于第一个参数 self 几乎总是指向调用方法的对象本身,因此 lua 提供了一种语法糖形式 object:method(...) 用于隐藏 self 参数的定义及传递。这里冒号的作用有两个,其在定义函数时往函数中地一个参数的位置添加一个额外的隐藏参数 sef, 而在调用时传递一个额外的隐藏参数 self 到地一个参数位置。 即 function object:method(v) end 等价于 function object.method(self,v) endobject:method(v) 等价于 object.method(object,v)

当涉及到类和继承时,就要用到元表和元方法了。事实上,对于 lua 来说,对象和类并不存在一个严格的划分。

当一个对象被另一个表的 __index 元方法所引用时,表就能引用该对象中所定义的方法,因此也就可以理解为对象变成了表的类。

类定义的一般模板为:

  1. function 类名:new(o)
  2. o = o or {}
  3. setmetatable(o,{__index = self})
  4. return o
  5. end

或者:

  1. function 类名:new(o)
  2. o = o or {}
  3. setmetatable(o,self)
  4. self.__index = self
  5. return o
  6. end

相比之下,第二种写法可以多省略一个表。

另外有一点我觉得有必要说明的就是 lua 中的元方法是在元表中定义的,而不是对象本身定义的,这一点跟其他面向对象的语言比较不同。

lua 表的更多相关文章

  1. LUA表克隆方法归纳

    lua表克隆 将lua一个表, 克隆出一份为一个独立的另外一个表. 对于一个module, 如果在require之后,获得的表对象, 不能直接修改, 例如lua缓存此表, 但是多次逻辑执行, 都使用的 ...

  2. <4>Lua表

    lua表 1: lua没有数组,但是表可以代替数组的功能(数组部分与非数组部分); 开始的, 1, 2, 3 ...称连续的索引; b.Lua表的连续索引的长度(数组部分);  #表的名字; --数组 ...

  3. Lua表(table)的用法_个人总结

    Lua表(table)的用法_个人总结 1.表的创建及表的介绍 --table 是lua的一种数据结构用来帮助我们创建不同的数据类型.如:数组和字典--lua table 使用关联型数组,你可以用任意 ...

  4. lua表排序

    对于lua的table排序问题,一般的按照value值来排序,使用table.sort( needSortTable , func)即可(可以根据自己的需要重写func,否则会根据默认来:默认的情形之 ...

  5. 对lua表中数据按一定格式处理,循环

    function putStartCard(handCard) function dataDeal(array,a,b,c) cclog("进入datadeal=============== ...

  6. lua表类型

    Lua的表的定义: typedef struct Table { CommonHeader; lu_byte flags; lu_byte lsizenode; /* log2 of size of ...

  7. LUA表与函数的深入理解

    local heroInfo = {} --直接打印 table的名字,就会输出该table的内存地址 print("表地址---------------",heroInfo) - ...

  8. LUA表的引用理解

    --lua中引用类型都是分配在堆上的 --因此,我们在使用LUA的table时,可尽可能的使用表的引用,而不需要拷贝表里的元素 --比如,通过RPC协议传来一个表A,我们想要缓存这个表,只需要保存该表 ...

  9. Lua表(table)的个人总结

    1.表的简介和构造 table是个很强大且神奇的东西,又可以作为数组和字典,又可以当作对象,设置module.它是由数组和哈希表结合的实现的.他的key可以是除nil以外任意类型的值,key为整数时, ...

随机推荐

  1. ceph对象存储RADOSGW安装与使用

    本文章ceph版本为luminous,操作系统为centos7.7,ceph安装部署方法可以参考本人其他文章. [root@ceph1 ceph-install]# ceph -v ceph vers ...

  2. 简单的Linq查询语句

    下面我来我大家介绍几种简单的查询方式. 1.简单语法 这个LINQ语句的第一个关键字是from,from后面加的是范围变量,范围变量后加in,后加上事先实例化的模型,然后点出数据的来源. List是列 ...

  3. MySQL统计库表大小

    统计每个库每个表的大小是数据治理的其中最简单的一个要求,本文将从抽样统计结果及精确统计结果两方面来统计MySQL的每个库每个表的数据量情况. 1.统计预估数据量 mysql数据字典库informati ...

  4. Pytest 单元测试框架标记用例

    1.Pytest 中标记用例 接参数 -k 来挑选要执行的测试项 pytest -k test_szdcs -s test_szdcs 为函数名称 -k 后面接的名称可以为函数名称.类名称.文件名称. ...

  5. linux系统,没有安装任何编辑器的情况,如何操作文件

    [清空文件] echo  " " > filename [添加内容] echo "123" >> filename

  6. C++ 简单的UDP客户端与服务端

    .h #pragma once #ifndef __C_UDP_OBJECT_H__ #define __C_UDP_OBJECT_H__ #define OS_PLATFORM_WIN #inclu ...

  7. Sightseeing,题解

    题目: 题意: 找到从s到t与最短路长度相差少于1的路径总数. 分析: 首先,搞明白题意之后,我们来考虑一下怎么处理这个1,怎样找相差为1的路径呢?我们这样想,如果有相差为1的路径,那么它将会是严格的 ...

  8. 代码注入——c++代码注入

    代码注入之——c++代码注入 0x00  代码注入和DLL注入的区别 DLL注入后DLL会通过线程常驻在某个process中,而代码注入完成之后立即消失. 代码注入体积小,不占内存 0x01  通过c ...

  9. python入门008

    目录 一.for循环 作用:for循环是因为在循环取值(即遍历值)时for循环比while循环的使用更为简洁 1.for循环语法: 2.应用案例: 注意:break 与 continue也可以用于fo ...

  10. 一篇文章掌握 Python 内置 zip() 的全部内容

    一篇文章掌握 Python 内置 zip() 的全部内容 zip() 是 Python 中最好用的内置类型之一,它可以接收多个可迭代对象参数,再返回一个迭代器,可以把不同可迭代对象的元素组合起来. 我 ...