您好,欢迎访问三七文档
捕获捕获是这样一种机制:可以使用模式串的一部分匹配目标串的一部分。将你想捕获的模式用圆括号括起来,就指定了一个捕获。在string.find使用捕获的时候,函数会返回捕获的值作为额外的结果。这常被用来将一个目标串拆分成多个:pair=name=Anna_,_,key,value=string.find(pair,(%a+)%s*=%s*(%a+))print(key,value)--nameAnna'%a+'表示菲空的字母序列;'%s*'表示0个或多个空白。在上面的例子中,整个模式代表:一个字母序列,后面是任意多个空白,然后是'='再后面是任意多个空白,然后是一个字母序列。两个字母序列都是使用圆括号括起来的子模式,当他们被匹配的时候,他们就会被捕获。当匹配发生的时候,find函数总是先返回匹配串的索引下标(上面例子中我们存储哑元变量_中),然后返回子模式匹配的捕获部分。下面的例子情况类似:date=17/7/1990_,_,d,m,y=string.find(date,(%d+)/(%d+)/(%d+))print(d,m,y)--1771990我们可以在模式中使用向前引用,'%d'(d代表1-9的数字)表示第d个捕获的拷贝。看个例子,假定你想查找一个字符串中单引号或者双引号引起来的子串,你可能使用模式'['].-[']',但是这个模式对处理类似字符串it'sallright会出问题。为了解决这个问题,可以使用向前引用,使用捕获的第一个引号来表示第二个引号:s=[[thenhesaid:it'sallright!]]a,b,c,quotedPart=string.find(s,(['])(.-)%1)print(quotedPart)--it'sallrightprint(c)--第一个捕获是引号字符本身,第二个捕获是引号中间的内容('.-'匹配引号中间的子串)。捕获值的第三个应用是用在函数gsub中。与其他模式一样,gsub的替换串可以包含'%d',当替换发生时他被转换为对应的捕获值。(顺便说一下,由于存在这些情况,替换串中的字符'%'必须用%%表示)。下面例子中,对一个字符串中的每一个字母进行复制,并用连字符将复制的字母和原字母连接起来:print(string.gsub(helloLua!,(%a),%1-%1))--h-he-el-ll-lo-oL-Lu-ua-a!下面代码互换相邻的字符:print(string.gsub(helloLua,(.)(.),%2%1))--ehllouLa让我们看一个更有用的例子,写一个格式转换器:从命令行获取LaTeX风格的字符串,形如:\command{sometext}将它们转换为XML风格的字符串:commandsometext/command对于这种情况,下面的代码可以实现这个功能:s=string.gsub(s,\\(%a+){(.-)},%1%2/%1)比如,如果字符串s为:the\quote{task}isto\em{change}that.调用gsub之后,转换为:thequotetask/quoteistochangethat.另一个有用的例子是去除字符串首尾的空格:functiontrim(s)return(string.gsub(s,^%s*(.-)%s*$,%1))end注意模式串的用法,两个定位符('^'和'$')保证我们获取的是整个字符串。因为,两个'%s*'匹配首尾的所有空格,'.-'匹配剩余部分。还有一点需要注意的是gsub返回两个值,我们使用额外的圆括号丢弃多余的结果(替换发生的次数)。最后一个捕获值应用之处可能是功能最强大的。我们可以使用一个函数作为string.gsub的第三个参数调用gsub。在这种情况下,string.gsub每次发现一个匹配的时候就会调用给定的作为参数的函数,捕获值可以作为被调用的这个函数的参数,而这个函数的返回值作为gsub的替换串。先看一个简单的例子,下面的代码将一个字符串中全局变量$varname出现的地方替换为变量varname的值:functionexpand(s)s=string.gsub(s,$(%w+),function(n)return_G[n]end)returnsendname=Lua;status=greatprint(expand($nameis$status,isn'tit?))--Luaisgreat,isn'tit?如果你不能确定给定的变量是否为string类型,可以使用tostring进行转换:functionexpand(s)return(string.gsub(s,$(%w+),function(n)returntostring(_G[n])end))endprint(expand(print=$print;a=$a))--print=function:0x8050ce0;a=nil下面是一个稍微复杂点的例子,使用loadstring来计算一段文本内$后面跟着一对方括号内表达式的值:s=sin(3)=$[math.sin(3)];2^5=$[2^5]print((string.gsub(s,$(%b[]),function(x)x=return..string.sub(x,2,-2)localf=loadstring(x)returnf()end)))--sin(3)=0.1411200080598672;2^5=32第一次匹配是$[math.sin(3)],对应的捕获为[math.sin(3)],调用string.sub去掉首尾的方括号,所以被加载执行的字符串是returnmath.sin(3),$[2^5]的匹配情况类似。我们常常需要使用string.gsub遍历字符串,而对返回结果不感兴趣。比如,我们收集一个字符串中所有的单词,然后插入到一个表中:words={}string.gsub(s,(%a+),function(w)table.insert(words,w)end)如果字符串s为hellohi,again!,上面代码的结果将是:{hello,hi,again}使用string.gfind函数可以简化上面的代码:words={}forwinstring.gfind(s,(%a))dotable.insert(words,w)endgfind函数比较适合用于范性for循环。他可以遍历一个字符串内所有匹配模式的子串。我们可以进一步的简化上面的代码,调用gfind函数的时候,如果不显示的指定捕获,函数将捕获整个匹配模式。所以,上面代码可以简化为:words={}forwinstring.gfind(s,%a)dotable.insert(words,w)end下面的例子我们使用URL编码,URL编码是HTTP协议来用发送URL中的参数进行的编码。这种编码将一些特殊字符(比如'='、'&'、'+')转换为%XX形式的编码,其中XX是字符的16进制表示,然后将空白转换成'+'。比如,将字符串a+b=c编码为a%2Bb+%3D+c。最后,将参数名和参数值之间加一个'=';在name=value对之间加一个&。比如字符串:name=al;query=a+b=c;q=yesorno被编码为:name=al&query=a%2Bb+%3D+c&q=yes+or+no现在,假如我们想将这URL解码并把每个值存储到表中,下标为对应的名字。下面的函数实现了解码功能:functionunescape(s)s=string.gsub(s,+,)s=string.gsub(s,%%(%x%x),function(h)returnstring.char(tonumber(h,16))end)returnsend第一个语句将'+'转换成空白,第二个gsub匹配所有的'%'后跟两个数字的16进制数,然后调用一个匿名函数,匿名函数将16进制数转换成一个数字(tonumber在16进制情况下使用的)然后再转化为对应的字符。比如:print(unescape(a%2Bb+%3D+c))--a+b=c对于name=value对,我们使用gfind解码,因为names和values都不能包含'&'和'='我们可以用模式'[^&=]+'匹配他们:cgi={}functiondecode(s)forname,valueinstring.gfind(s,([^&=]+)=([^&=]+))doname=unescape(name)value=unescape(value)cgi[name]=valueendend调用gfind函数匹配所有的name=value对,对于每一个name=value对,迭代子将其相对应的捕获的值返回给变量name和value。循环体内调用unescape函数解码name和value部分,并将其存储到cgi表中。与解码对应的编码也很容易实现。首先,我们写一个escape函数,这个函数将所有的特殊字符转换成'%'后跟字符对应的ASCII码转换成两位的16进制数字(不足两位,前面补0),然后将空白转换为'+':functionescape(s)s=string.gsub(s,([&=+%c]),function(c)returnstring.format(%%%02X,string.byte(c))end)s=string.gsub(s,,+)returnsend编码函数遍历要被编码的表,构造最终的结果串:functionencode(t)locals=fork,vinpairs(t)dos=s..&..escape(k)..=..escape(v)endreturnstring.sub(s,2)--removefirst`&'endt={name=al,query=a+b=c,q=yesorno}print(encode(t))--q=yes+or+no&query=a%2Bb+%3D+c&name=al转换的技巧(TricksoftheTrade)模式匹配对于字符串操纵来说是强大的工具,你可能只需要简单的调用string.gsub和find就可以完成复杂的操作,然而,因为它功能强大你必须谨慎的使用它,否则会带来意想不到的结果。对正常的解析器而言,模式匹配不是一个替代品。对于一个quick-and-dirty程序,你可以在源代码上进行一些有用的操作,但很难完成一个高质量的产品。前面提到的匹配C程序中注释的模式是个很好的例子:'/%*.-%*/'。如果你的程序有一个字符串包含了/*,最终你将得到错误的结果:test=[[chars[]=a/*here;/*atrickystring*/]]print(string.gsub(test,/%*.-%*/,COMMENT))--chars[]=aCOMMENT虽然这样内容的字符串很罕见,如果是你自己使用的话上面的模式可能还凑活。但你不能将一个带有这种毛病的程序作为产品出售。一般情况下,Lua中的模式匹配效率是不错的:一个奔腾333MHz机器在一个有200K字符的文本内匹配所有的单词(30K的单词)只需要1/10秒。但是你不能掉以轻心,应该一直对不同的情况特殊对待,尽可能的更明确的模式描述。一个限制宽松的模式比限制严格的模式可能慢很多。一个极端的例子是模式'(.-)%$'用来获取一个字符串内$符号以前所有的字符,如果目标串中存在$符号,没有什么问题;但是如果目标串中不存在$符号。上面的算法会首先从目标串的第一个字符开始进行匹配,遍历整个字符串之后没有找到$符号,然后从目标串的第二个字符开始进行匹配,……这将花费原来平方次幂的时间,导致在一个奔腾333MHz的机器中需要3个多小时来处理一个200K的文本串。可以使用下面这个模式避免上面的问题'^(.-)%$'。定位符^告诉算法如果在第一个位置没有没找到匹配的子串就停止查找。使用这个定位符之后,同样的环境也只需要不到1/10秒的时间。也需要小心空
本文标题:LUA的捕获机制
链接地址:https://www.777doc.com/doc-2886274 .html