笔记: Lua基础: Metatable, 多重返回值, 迭代器

作者: apex.Cliz 

metatable

在一些博客上看到这个词被译作元表,我更偏向把它称作重载表,因为metatable的作用更像是重载(override)对应表的操作行为的(比如+,*).

构成metatable的方式是一个metatable挂接一个table,如下所示:

tbl1 = {"alpha","beta","gamma"}
mt = {}
setmetatable(tbl1,mt)

可以用getmetatable()语句来检视一个table所挂接的metatable.

> print(getmetatable(tbl1) == mt)
true

metatable通过其包含的函数来给所挂接的table定义一些特殊的操作,包括:

__add: 定义所挂接table的加法操作
__mul: 定义乘法操作
__div: 定义除法操作
__sub: 定义减法操作
__unm: 定义负操作,即: -table的含义
__tostring: 定义当table作为tostring()函式之参数被呼叫时的行为(例如: print(table)时将呼叫tostring(table)作为输出结果)
__concat: 定义连接操作(".."运算符)
__index: 定义当table中不存在的key值被试图获取时的行为
__newindex: 定义在table中产生新key值时的行为


__add,__mul,__div,__sub,__unm

例如,可以用下方的语句定义table的加法行为并使用它.

function mt.__add(a,b)
 local result = setmetatable({},mt)
 
 for i = 1,#a do
  table.insert(result,a[i])
 for i = 1,#b do
  table.insert(result,b[i])

 return result
end

> tbl1 = {"alpha","gamma"}
> tbl2 = {"delta","epsilon","zeta"}
> setmetatable(tbl1,mt)
> setmetatable(tbl2,mt)
> add_test = tbl1 + tbl2
> print(#add_test)
6
> for i = 1,#add_test do print(i,addtest[i]) end
1,alpha
2,beta
3,gamma
4,delta
5,epsilon
6,zeta

mul,div,sub,unm的操作方式也比较类似,执行自定义的运算过程,最后返回一个table.


__tostring

如前所述,__tostring函式用于处理当table作为tostring()函式之参数时的行为. 很显然,__tostring函式应该返回一个字符串. tostring函式需要一个参数(代表作为参数的table).

function mt.__tostring(tbl)
 local result = "{"
 
 for i = 1,#tbl do
  if i > 1 then
   result = result .. ","
  end

  result = result .. tostring(tbl[i])
 end

 result = result .. "}"
 return result
end

> tbl1 = {"alpha",mt)
> print(tbl1)
{alpha,beta,gamma}
> print(tbl2)
{delta,epsilon,zeta}


__index

当table中不存在的key值被访问时,系统将返回nil(空). 在一些情况下,可能希望返回更有意义的内容. 在这种情况下,可以通过__index来达到这个效果.

__index的行为如下: 在table中不存在的key值被访问时呼叫__index,例如: value = tbl.undefined,此时将呼叫__index(如果其存在,否则返回nil).

__index可以指向另一个table; 在这种情况下,当其他语句访问原table中不存在的key值时,会得到__index所指向的table中同名的key值. 例如:

deDE_races = {
 ["Night elf"] = "Nachtelf"
}
mt = {}
setmetatable(deDE_races,mt)

enUS_races = {
 ["Human"] = "Human",
 ["Night elf"] = "Night elf",
}

mt.__index = enUS_races

> print(deDE_races["Night elf"])
Nachtelf
> print(deDE_races["Human"])
Human

当deDE_races表中不存在的key值(deDE_races.Human)被访问时,返回了__index所指向的另一个table中同名的key值(enUS_races.Human).

这是一种很方便的定义默认值的方法. 若将所有默认值在一个default_tbl中定义好,以后其他同类型的table全部以__index挂接default_tbl,则无需在每个table中重新设置所有key的默认值; 当未设定的某key被访问时,__index将指导其自动返回预设表中的key值.

除了挂接到另一个table以返回“默认值”外,__index还可以指向一个函数,以处理包括返回错误信息等更加复杂的工作. 在此种情况下,__index必须携带两个参数,依次为被访问的table及被访问的key值,并必须返回一个值. 例如,可以写一个这样的函数来产生错误信息:

function mt.__index(tbl,key)
 print("ERROR: Attempt to access undefined key '"..key.."' in " .. tostring(tbl))
 return nil
end


__newindex

在一些特定的情况下,可能需要限制table中的key进行赋值的行为(包括产生新key值), 可以用__newindex来处理这个问题. __newindex携带三个参数: 在赋值行为中的table,key及赋予的值(value).

例如,以下的函式可以防止在所挂接的table中建立banana这个key值:

function mt.__newindex(tbl,key,value)
 if key == "banana" then
  error("ERROR: Cannot set a protected key")
 else
  rawset(tbl,value)
 end
end

这样,当试图建立这个key值时,会得到如下的报错信息:

> tbl1.banana = "yellow"
stdin: 3: ERROR: Cannot set a protected key
stack traceback:
    [C]: in function 'error'
    stdin:3: in function
    stdin:1: in main chunk
    [C]: ?
> print(tbl1.banana)
nil

这里用到了rawset函数,与其相关的还有rawget函数,这两个函数会在忽略所挂接的metatable的前提下,存取table中的key值,其语法分别为:

value = rawget(tbl,key)
rawset(tbl,value)


多重返回值

在魔兽世界中,有一些时候我们会需要一个函数返回超过一个值. 看下面的例子:

颜色在界面编程中是一项很重要的数值. 常见的颜色方法是用六位的十六进制数字,每两位依次表示三原色中一种颜色在最终混合而成的颜色中所拥有的强度,从0(00)到255(FF). 在有些情况下,可能会分别需要三原色每种色的强度数值. 在获取了六位十六进制数字后,我们可以自己写一个函式返回分开的三种颜色的强度值.

例如说,颜色#99CCFF可以被表示为(0.6,0.8,1.0),其中1.0代表最大强度(255).
要设计一个这样的函式,首先要涉及到以下的两个函式: string.sub()以及tonumber().
string.sub()函式用于取得给定字符串的子字符串,而tonumber()可以将字符串以给定的进制转化为10进制数字.

这样,我们得到下面的函式代码:

function ConvertHexToRGB(hex)
 local red = string.sub(hex,1,2)
 local green = string.sub(hex,3,4)
 local blue = string.sub(hex,5,6)

 -- Lua无敌的变量类型可变的优势在这里完美的体现了...
 red = tonumber(red,16) / 255
 green = tonumber(green,16) / 255
 blue = tonumber(blue,16) / 255
 
 return red,green,blue -- 返回多重值的语句写法
end

函数的使用结果如下:

> print(ConvertHexToRGB("FFCC99"))
1,0.6
> print(ConvertHexToRGB("FFFFFF"))
1,1
> print(ConvertHexTORGB("000000"))
0,0

如果需要将这个函数返回的值赋给其他的变量,可以这样写:

red,blue,green = ConvertHexToRGB("FFCC99")

要注意的是,等号右边也可以有多个数值,它们看起来应该是和左边一一对应赋值的; 但包括返回多重值的函数时,情况将发生变化.

red,alpha = ConvertHexToRGB("FFCC99"),"yet","another","argument"

在这个语句执行之后,并不表示alpha将得到"yet",而多余的两个字符串值将被抛弃; 出人意料的,只有red正确地接收了函数返回的值,而blue,alpha则分别接收了"yet","another"和"argument"三个字符串.

用具有多重返回值的函数作为参数时,也有类似的情况:

> print(ConvertHexToRGB("FFFFFF"))
1,1
> print(ConvertHexToRGB("FFFFFF"),"SomeOtherArgument")
1,SomeOtherArgument

出现这个情况的理由是这样的: 因为一些技术上的原因,在Lua中,类似的情况下一个具有多重返回值的函数必须位于参数列表的末尾,否则它将只返回第一个值. 在第二个语句中,ConvertHexToRGB("FFFFFF")后面还有其他的参数,于是它只返回了第一个值.

附注: 如果特意只需要第一个返回值,可以用下面的方法,即: 为函数语句多增添一对括号:

> print((ConvertHexToRGB("FFFFFF")))
1

有很多魔兽世界中的函式都具有多重返回值,例如函式GetRaidRosterInfo()以玩家在raid中的编号为参数,并将返回包括玩家姓名、团队职位(团长、团长助手或一般成员)、所在小队、角色等级、职业、目前所在地区、是否在线、是否死亡等等讯息.

一个函式可能会返回如此多的值,但并不代表只为了利用其中的某一个值(不是第一个被返回的值),就必须列一排的变量把结果全部接收下来. 当然在函式返回的值并不是太多的时候,还是可以用这样的方法; 因为Lua并没有限制一个变量的类型(在赋值的同时就自动更换了),可以用一些不太会用到的名字命名一个回收箱变量,然后把不需要的值都丢到里面去. 这种做法被称作虚拟变量法(dummy variable). 例如,在下面的语句中,用了单下划线这样正常情况下不会用到的名字来命名一个虚变量,用它来丢弃不需要的数值.

local _,g = ConvertHexToRGB("FFFFFF")

如同预期,g得到了函式返回的对应绿色(green)的颜色强度值,而排在其之前被返回的红色强度值则被丢弃到不会用到的变量"_"当中去了.

明显的,如果函式有了如同前面所说的数量庞大的返回值,这种方法就一点都不好用了; 你需要浪费存储空间和语句来建立一大堆名字不同的垃圾箱,只是为了丢弃不需要的变量. Lua提供了一个更优秀的解决方案: 函式select().

select()函式接收一个参数n,用来指定一个起始点; 然后select函式将返回指定的多重返回值序列中起点开始到序列末尾为止的部份. 如只需要起始处的元素,则再对函数语句增加一对括号即可.

下面的代码简单地体现了select函式的功能.

> print(select(1,"alpha","gamma"))
alpha,gamma
> print(select(2,"gamma"))
beta,gamma

之前提过,函数可以有不确定个数的参数(笔记: Lua基础: 函数,控制流: "特别地,Lua支持不确定个数的参数列表..."),select()也可以用来帮助处理这里的参数列表.

select()函数的第一个参数,除了是返回列表的起始点外,还可以是字符"#". 在这种情况下,它返回后续参数列表的长度:

> print(select("#","gamma"))
3

可以用这样的方法产生类似foreach的功能:

function printargs(...)
 local num_args = select("#",...)
 for i = 1,num_args do
  local arg = select(i,...)
  print(i,arg)
 end
end

> printargs("alpha","gamma")
1,gamma

这个功能比之前的foreach实现方式(for i=1,#tbl do)有一个优点: 可以在参数列表中放入空值(nil).

用#tbl实现时,如果遇到nil元素,将导致循环中止,但用select语句实现的话,循环不会中止.

function printargs2(...)
 local tbl = {...}
 for i = 1,#tbl do
  print(i,tbl[i])
 end
end

> printargs2("alpha",nil,alpha
> printargs("alpha",nil
3,gamma


迭代器

目前为止提到的遍历功能都有一个共同的缺陷,即: 它们都只支持table中标准array结构的部份,而对以string为key的部份(根据它的实现方式,也可以称为Hash部份)是无能为力的. 这并不代表不能把这一部份纳入遍历当中; 只是需要换另一种方式来写. 这样的功能需要内建的迭代器(iterator)函式来予以实现.

迭代器是"一个对象,可以让程序编写人员遍历一个集合中的所有元素,不管这个元素技术上的实现方式如何".

(Wikipedia: An iterator is an object which allows a programmer to traverse through all elements of a collection,regardless of its specific implementation.)

实际使用时仍依赖for函数,此时的for格式有所不同,称作generic for.

for val1,val2,val3,... in <expression> do
 -- body of the for loop
end

其中在<expression>标签处放置的是迭代表达式,此表达式返回以下三个值:

在每个迭代循环中被呼叫的函数; 迭代循环的初态; 迭代变量的初始值.

例如,迭代表达式ipairs()返回下面的内容:

> print(ipairs(table))
function: 0x300980,table: 0x3072c0,0
> print(table)
table: 0x3072c0

幸运的,除非需要自己写迭代器,这三个值一般不需要深入了解. 系统内部会自动处理并生成所需的迭代器.

ipairs()

ipairs()函数用于遍历table中的数组部分.

> tbl = {"alpha","gamma"}
> for indx,value in ipairs(tbl) do
>> print(idx,value)
>> end
1,gamma

ipairs()函数以一个table为参数并返回for循环遍历table所需的全部信息. 每次呼叫迭代函数时均获得一个元素的索引值,及元素本身.

for后面的变量名称可以任意,呼叫迭代函数所返回的值将依序填充到变量列表中的变量中去. 这些变量的作用范围仅限于for循环体.

 

pairs()

pairs()函数基本和ipairs()函数用法相同,区别在于pairs()可以遍历整个table,即包括数组及非数组部分.

> tbl = {"alpha",["one"] = "uno",["two"] = "dos"}
> for key,value in pairs(tbl) do
>> print(key,beta
one,uno
two,dos

受到哈希表实现方式的影响,此遍历过程的顺序和元素加入表的顺序是无关的. 上述结果与输入顺序相同只是巧合.

注意: pairs()函数在遍历时会呼叫table内建的next()函数,如果在pairs()对table进行遍历的过程中对table进行加值操作,将使next函数不能正常工作,并使遍历终止或提前结束.

利用pairs()的遍历还可以清空一个table,如:

for key,value in pairs(tbl) do
   tbl[key] = nil
end

 

string.gmatch()

string.gmatch()类似是Lua中的"正则表达式配对",可以用来配对并获取原始字符串中的部分.

> for word in string.gmatch("These are some words","%S+") do
>> print(word)
>> end
These
are
some
words
> for char in string.gmatch("Hello!",".") do
>> print(char)
>> end
H
e
l
l
o
!

详细的配对规则到下一篇笔记的时候再写.

Tag标签: Lua

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


1.github代码实践源代码是lua脚本语言,下载th之后运行thmain.lua-netTypevgg-savevgg_cifar10/-S0.0001,报错: 试看看安装lua:报错了,参考这篇文章:ubuntu18.04安装lua的步骤以及出现的问题_weixin_41355132的博客-CSDN博客问题解决,安装成功:情况并没有好转,出现相
此文为搬运帖,原帖地址https://www.cnblogs.com/zwywilliam/p/5999924.html前言在看了uwa之前发布的《Unity项目常见Lua解决方案性能比较》,决定动手写一篇关于lua+unity方案的性能优化文。整合lua是目前最强大的unity热更新方案,毕竟这是唯一可以支持ios热更新的办法。然而作
Rime输入法通过定义lua文件,可以实现获取当前时间日期的功能。1.TIMERime是一款可以高度自定义的输入法,相关教程可以查看往期文章,关于时间获取是指输入一个指定关键字,输出当前时间,效果如下(我定义了time关键字):实现如下:①在用户文件夹中新建一个rime.lua文件加入如下代码 ti
localfunctiongenerate_action(params)localscale_action=cc.ScaleTo:create(params.time,params.scale_x,params.scale_y)localfade_action=cc.FadeIn:create(params.time)returncc.Spawn:create(scale_action,fade_action)end
2022年1月11日13:57:45 官方:https://opm.openresty.org/官方文档:https://opm.openresty.org/docs#table-of-contents为什么建议使用opm不建议使用luarocks?http://openresty.org/cn/using-luarocks.html官方解释:请注意!LuaRocks并不是OpenResty官方推荐的装包方式。LuaRoc
在Lua中的table(表),就像c#中的HashMap(哈希表),key和value一一对应。元表:table的一个操作的拓展,里面包含关联了对应的方法,元方法就是其中一个。元方法:当你通过键来访问table的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)中的__index键。如果__inde
表排序:table.sort(list[,comp])参数list:指定表,可选参数comp:排序函数,无参数时通常按升序排序。排序函数针对表中连续的序列,其间不可以存在空洞或nil,排序函数需要两个形参(对应表中每次参加比较的两个数据),需要一个比较两个形参表达式的返回值,不能含有等于关系,例如>=,<=,==。do
一、安装lua环境1.1安装依赖包[root@centos7~]#yuminstallgccreadline-devel1.2下线lua源码包并解压[root@centos7~]#wgethttp://www.lua.org/ftp/lua-5.3.5.tar.gz[root@centos7~]#tarxvflua-5.3.5.tar.gz-C/usr/local/src1.3进行编译[root@centos7~]
官网OpenResty® 是一个基于 Nginx 与Lua的高性能Web平台,其内部集成了大量精良的Lua库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态Web应用、Web服务和动态网关。OpenResty® 通过汇聚各种设计精良的 Nginx 模块(主要由
表参考《lua程序设计》可以认为,表是一种动态分配的对象,程序只能操作指向表的引用(或指针)。除此以外,Lua语言不会进行隐藏的拷贝(hiddencopies)或创建新的表--创建表a={}--创建空表k="x"a[k]=10--键“x”值10a[20]="great"--键20值“great”print(a["x"])-->10
https://github.com/galenho/crossover.git一个跨平台的lua游戏服务器开发框架,该框架采用多线程并发来处理消息,开发者只需要调用相应的接口函数并绑定相应的回调函数即可,在逻辑层表现为单线程的开发模式,使开发者易用,易调试,易维护,易扩展,同时拥有快速的响应能力。   框架使用面
参考链接:https://www.runoob.com/lua/lua-metatables.htmlhttps://www.jianshu.com/p/cb945e7073a3 元表是一个table,可以让我们改变table的行为,每个行为有对应的元方法例如,对table进行设置键值,查找键值,运算等,就会触发对应的元方法1--__index:table被访问时,如果找不到这
https://github.com/yuin/gopher-luahttps://github.com/yuin/gopher-lua Lua5.1ReferenceManual-contentshttp://www.lua.org/manual/5.1/ go中使用luapackagemainimport( lua"github.com/yuin/gopher-lua")funcmain(){ l:=lua.NewState() d
编译问题不要留到运行时才跑出来啊。早上9:00-中午3:00,6个小时,服了自己了。 写了一个测试,springboot+redis+lua执行到redisTemplate.execute(redisScript,idList)的时候一直报错,integer无法转换为string。我一直以为是lua脚本写错了,翻文档翻过来又翻过去,写法变了又变,还是解
        。。是字符串连接符,字典用=号连接,  注意fordoend都是连一起,  注意ifthen,  如果local在函数里,是可以访问,非local出了函数一样能用,  doend代码块也是一样,    注意点号表示,只能key是字符串,  注意括号不是必须
C语言与Lua之间的相互调用详解写一个C调用Lua的Demo编译运行C语言调用Lua编译问题总结正确的编译命令问题1:缺少-lm参数问题2:缺少-ldl参数​1、为什么会出现undefinedreferenceto‘xxxxx’错误?​2、-l参数和-L参数写一个C调用Lua的Demo编译运行add.c内容//你需要
1、动态输出打开E:\study\openresty\openresty-1.19.9.1-win64目录下的confginx.conf文件在server中增加一下代码 location/hello{ default_typetext/html; content_by_lua'ngx.say("<p>hello,world</p>")'; }运行后,效果如下图localhost
参见:lipp/lua-websockets:WebsocketsforLua.(github.com)github网址可能需手动转换lipp.github.com/lua-websockets/>github.com/lipp/lua-websocketswebsockets为底层的类似于TCP、UDP的socket(实现上基于更底层的socket),不同于上层的webserver服务端(Service)需并行地支持多
lua发送消息到rabbitmq,我们选择类库lua-resty-rabbitmqstomp 来完成这个任务。类库安装:进入nginx.conf中 lua_package_path 中对应的目录下的resty目录(没有则创建),执行:wget-chttps:/aw.githubusercontent.com/wingify/lua-resty-rabbitmqstomp/master/libes
1Lua介绍Lua是一门以其性能著称的脚本语言,被广泛应用在很多方面。Lua一般用于嵌入式应用,现在越来越多应用于游戏当中,魔兽世界,愤怒的小鸟都有用到。优势Lua极易嵌入到其他程序,可当做一种配置语言。提升应用性能,比如:游戏脚本,nginx,wireshark的脚本兼容性强,可以直接使用C