日期:2014.7.24
PartⅢ The Standard Libraries
21 The String Library
Lua的string标准库提供了完整的对string型变量进行操作的方法。string库将其操作函数输出为一个叫做string的模块,而从lua的5.1版本开始,也将这些函数输出为string型变量的方法(这里涉及到元表),因此使用string标准库中的函数有两种方法,这里以upper函数为例:string.upper(s)
21.1 Basic String Functions
string.len(s) 计算s的长度,与#s的结果一样。
string.rep(s,n)/s:rep(n) 返回重复n次的s。如print(string.rep("a",5)) -- aaaaa
string.lower(s) 返回s的小写版
string.upper(s) 返回s的大写版
string.sub(s,i,j) /s:sub(i,j) 从s中抽取出从index i至j位的字符。
string.char(s) 、string.byte(s,i) 在字符/数字 之间进行转换。string.char获取0个或多个整数,将每一个数字转换成字符,返回这些字符连接起来的字符串;string.byte(s,i)将字符串s的第i个字符转换成整数,默认i = 1.从lua5.1开始,string.byte接受第三个参数--string.byte(s,i,j),将字符串s中的第i至j个字符转换成整数,返回值为这些整数
e.g. print(string.char(97)) -- a i = 99; print(string.char(i,i+1,i+2)) -- cde print(string.byte("a")) --97 {s:byte(1,-1)} --这将以s中所有的字符转换的整数来创建一个 tablestring.char(table.unpack(t)) --这个相当于上一步的逆向运算
string.format()用来转换string型变量的格式。具体的使用规则与C中的类似。
这里要注意的是,所有对string型的变量进行的操作不会对传进去的参数进行修改,而是会创建一个新的string型变量再返回,如果需要修改原数据,则需要通过赋值来实现。
21.2 Pattern-Matching Functions
模式匹配??
string标准库中的find,match,gsub(global substitution),match(global match)函数都是基于模式匹配的?
string.find函数
该函数从给定的string型变量中查找一个待搜索的模式串。组成一个最简单的模式串就是用一个word(单词),如模式串为"hello",被查找的字符串"hello world",该函数将会从中查找hello这个单词。当查到到了,该函数将会返回两个值:匹配到的起始index,和最终index,如果没有查找到将会返回nil:
e.g. s = "hello world" i , j = string.find(s,"hello") --string的起始index为1 print(i,j) --1 5 print(string.sub(s,i,j)) --hello 提取s中index 为1至5的字符,组成一个新的字符串返回
该函数接受第三个可选参数:一个index值用来告诉find函数开始查找的起始位置,默认是1.:
local t = {} local i = 0 while true do i = string.find(s," ",i+1) --每一次从上一次查找到的位置开始进行新的一次查找 if i == nil then break end t[#t + 1] = i end
string.match函数
该函数也是对给定的string型变量进行查找,但是返回值不是查找到的位置,而是直接将查到到的字符串返回,即
e.g. print(string.match("hello world","hello")) --这里将会打印 hello 一个很牛逼很强大的用法是: e.g. data = "Today is 17/7/1990" d = string.match(data,"%d+/%d+/%d+") print(d)
关于传进去的模式串的写法之后会做进一步的解释
string.gsub函数
该函数强制需要三个参数:源字符串、模式串、替代字符串。使用示例:
e.g. s = string.gsub("lua is cute","cute","greate") print(s) s = string.gsub("all lii","l","x") print(s) s = string.gsub("Lua is greate","Sol","Sun") print(s) 当然有第四个可选参数,用来限制替换的数量 e.g. --替换一个 s = string.gsub("all lii","l","x",1) print(s) --替换两个 s = string.gsub("all lii","l","x",2) print(s) 该函数也返回第二个值,表示替换的次数 e.g. s ,i= string.gsub("all lii","l","x") print(s,i) --axx xii 3
string.gmatch函数
该函数返回一个函数迭代遍历所有源字符串里出现的模式串字符。例子:
words = {} for w in string.gmatch(s,"%a+") words[#words + 1] = w end
这个函数会将s内的所有单词存储至words中。这里的模式串"%a+"的意思是表示匹配标准是一个单词(与任何字母配对)
21.3 Patterns
使用字符类可以让模式串发挥出更大的作用。字符类是一种在给定格式下匹配任何字符的模式串。例如%d可以匹配任何的数字,因此假如我们想匹配dd/mm/yyy这种格式,可以写这样的模式串:%d%d/%d%d/%d%d%d
e.g. s = "Deadline is 30/05/1999,firm" data = "%d%d/%d%d/%d%d%d" print(string.match(s,data)) --30/05/1999
一下列出了所有的字符类:
. 所有的字符
%a 字母
%c 控制字符
%d 数字
%g 除空格外所有能打印的字符
%l 小写字母
%p 标点符号
%s 空白字符
%u 大写字母
%w 任何字母、数字
%x 十六进制数
使用大写就相当于取其补集:%A 相当于匹配所有非字母的
e.g. print(string.gsub("hello, up-down!","%A",".")) --这里会将所有非字母的字符替换为"."
还有一些特殊的字符类,作者称之为magic characters:
() . % + - * ? [] ^ $
%相当于对这些magic characters做分隔的分隔符,如:%. 将会匹配"."这个符号,%%将会匹配"%",这个字符不仅可以用来分隔这里提到的magic character,还可以用来分隔任何非字母数字的字符。
Lua中的模式串跟一般的字符串一致,只是在模式匹配函数中才会发挥出其作用,而且只会以"%"符号作为其escape(分隔符?)
可以使用char-set技术来创建定制的字符类,使用方法是[ ]进行组合,如[%new_set] 这样就创建了一个新的字符类。[%w_]这个字符类用来匹配任何字母、数字字符和下划线。
也可以设置一个匹配范围,在第一个和最后一个字符之间使用连接符:[0-7] 此时将会匹配0-7范围内的数值,这里需要使用 "%" 符号。
另外结合以下几个符号,将会使字符类变得更高效:
+ 一个或多个重复
* 0个或多个重复
- 0个或更少的重复
? 可选匹配(0次或1次匹配)
使用 "+"将会匹配一个或多个字符。意思是将会取最长的去匹配,如使用"%a+"将会匹配一个单词(匹配最长范围,预空格结束)
print(string.gsub("one,and two;and three","%a+","word")) --将会将全部单词替换为word
"*"的作用与"+"类似,只是这个符号将会接受0次重复的情况,在匹配括号的时候,可能是(),也可能是( ),此时采用"*"便能匹配到空格符0次重复的()情况,%(%s*%)
"-"也能匹配到0次重复的情况,但这个符号更多的是做取最短的匹配:字符类为/%*.-%*/",此时将会匹配/**/组合的最短字符组合,如
e.g. test = "int x; /* x */ int y; /* y */" print(string.gsub(test,"/%*.-%*/","")) --每次匹配的时候取/**/组合的最短串,这样就能将/**/组成的最短字符串替换掉。
"?" 匹配的是可选字符,如想从一个文件中找出一个数字,该数字可选是否带正负号,此时就可以将字符类设计为:[+-]?%d+ 这样正负号就是可选的了。
如果一个模式串以"^"开头表示此时只会对源字符串的开头进行匹配,如果以"$"开头,表示只会对源字符串的结尾进行匹配。
<span style="font-size:18px;">e.g. string.find(s,"^%d") --检查s是否是以数字开头的</span>
"%b",做对称匹配,如"%bxy",去匹配源字符串是否是以x为开头以y为结尾的部分:
e.g. a = "a (enclosed (in) parentheses) line" print(string.gsub(a,"%b()","")) --将()包含的部分替换掉。
"%f[char-set]",所谓的frontier pattern(边界匹配),该匹配条件是当下一个字符是char-set的而前一个不是的时候匹配一个空的字符串:
e.g. s = "the anthem is the theme" print(s:gsub("%f[%w]the%f[%W]","one")) --注意这里的两个字符类w和W
%f[%w]匹配边界为介于一个非字母数字字符和一个字母数字字符之间;%f[%W]匹配边界为介于一个字母数字字符和一个非字母数字字符之间。感觉被绕晕了,这里一定要注意的是char-set一定要写在[]里面。
21.4 Captures
捕获机制是用来允许模式串抽取源字符串的一部分用来匹配当前的模式串,并为下一步使用做准备。语法规则是在模式串中在()里面写要捕获的部分。
当一个模式串里面写了捕获串的时候,string.match函数将会返回每一个捕获到的值,换句话来说就是将源字符串根据捕获串来做分解
e.g. pair = "name = Anna" key,value = string.match(pair,"(%a+)%s*=%s*(%a+)") print(key,value)
这里的模式串"(%a+)%s*=%s*(%a+)",来稍微分析一番
(%a+)这里是一个捕获列表,以()括住了要捕获的部分,这里要捕获的是单词(%a+取最长)
%s*=%s* 这里匹配等于号"="前后所有的空白字符
(%a+) 再一次捕获单词
所以整个模式串匹配的是:一个字母组+空格组+等于号+空格租+一个字母组。
而此时match函数返回的是捕获到的值,所以此时返回值是name 和 Anna"(["'])(.-)" 这个模式串??
Lua的模式串格式还是很复杂的,需要多注意!感觉一时半会还不会用啊
gsub中使用捕获值
与模式串一样,gsub中用来替代的字符串也可以包含如"%d"这样的字符类.当替代的字符串创建后这些字符类就会修改各自的捕获值。特别的是,"%0"将会修改所有匹配到的。以下例子中,函数将会以一个连接符分隔复制出源字符串的所有字符:
e.g. print(string.gsub("hello Lua!","%a","%0-")) --打印的是:h-e-l-l-o- L-u-a-!
我的理解是,每次匹配一个字符,替代的是其本身加上一个连接符(%0表示匹配到的所有字符,这里的意思就是用匹配到的字符+一个连接符替代捕获到的字符)
以上还需要进一步理解
e.g. print(string.gsub("hello lua","(.)(.)","%2%1")) --打印:ehll oula
我的理解是:两两交换(意思是每次捕获两个任意字符,%2代表的是第二个字符,%1代表的是第一个字符,%2%1表示将捕获到的两个字符,互换位置,所以就实现了两两交换的目的)
e.g. s = [[the quote{tast} is to em{change} that.]] s = string.gsub(s,"\(%a+){(.-)}","<%1>%2</%1>") print(s) --the <quote>tast</quote> is to <em>change</em> that.
理解一下这个模式串:"\(%a+){(.-)}",这里前面的双\斜线,第一个是用来控制格式的,如
表示新一行。之后接一个捕获值列表捕获一个单词,然后是双括号里面加一个捕获列表,捕获最短的任意字符。
替换串:"<%1>%2</%1>",将捕获到的第一个值,第二个值,分别以格式<xx>yy</xx>替换到匹配的字符。
21.5 Replacements
我们不仅可以使用字符串作为gsub函数的第三个参数,还可以使用table或者一个函数作为参数类型。当参数是一个函数,那么gsub函数将会在每次匹配到了的时候就调用该函数,捕获到的值将作为函数的参数,而函数的返回值将作为替换字符串。当参数是一个table的时候,gsub函数以捕获到的第一个值作为key来访问table,而该key指向的value则为用来替代的字符串.如果从函数调用或者访问table得到的返回结果是nil,则gsub函数不会替换源字符串的字符。
e.g. function expand( s ) return (string.gsub(s,"$(%w+)",_G)) --从全局变量中寻找捕获到的这个变量的名字,找到了就替换掉,而当没有找到的时候不做任何替换 end name = "lua";status = "greate" print(expand("$name is $status,isn't it?"))
可以使用tostring函数强制将捕获到的值转换为string类型,以用来做替换,保证代码的稳定性。
e.g. function expand( s ) return (string.gsub(s,"$(%w+)",function ( n ) return tostring(_G[n]) end)) end print(expand("print = $print;a = $a"))
将LaTex格式的文件转换为XML格式,之前已经做了一次实现,在这里则考虑到了嵌套的情况:
--to xml function toxml( s ) s = string.gsub(s,"\(%a+)(%b{})",function ( tag,body ) body = string.sub(body,2,-2) --移除大括号 body = toxml(body) --处理嵌套情况 return string.format("<%s>%s</%s>",tag,body,tag) end) return s end LaTex = [[ itle{The old{big} example}]] print(toxml(LaTex))
URL encoding
URL编码:HTTP在URL中用来发送消息的编码。这种编码方式将一些特殊字符(如 '=','&','=')表示为"%xx",xx为16进制中表示的字符,并且将空格转换为'+',例如,a+b = c 将会被表示为:a%2Bb+%3D+c。这种编码方式会将每一个参数名和这个参数的值使用一个等号连接起来,并且将所有的参数名及其值用‘&’连接起来,例如:
name = "al"; query = "a+b = c"; q = "yes or no" 将会被表示为
name=al&query=a%2Bb+%3D+c&q=yes+or+no
注:=的ASCII十进制表示为61,对应的16进制表示为3D
+的ASCII十进制表示为43,对应的16进制表示为2B
现在假如我们想将这些URL解码并且存储值table中,以其相对应的名字作为index值,以下做具体实现:
function unescape( s ) s = string.gsub(s,"+"," ") --将'+'替换为空格 s = string.gsub(s,"%%(%x%x)",function ( h ) --将捕获到的16进制数值先转换成10进制数,再将十进制数转换为转换为ASCII字符 return string.char(tonumber(h,16)) end) return s end print(string.char(tonumber("2B",16))) --这里将16进制表示的字符转换为十进制数
而对name = value格式进行解码则使用gmatch,因为包括name和value都不能呢个包含字符'&'和'=',所以使用的模式串应该为:'[^&=]+'
e.g. --decode pairs-name = value cgi = {} function decode( s ) for name,value in string.gmatch(s,"([^&=]+)=([^&=]+)") do --当gmatch的模式串里包含捕获值的时候,gmath将会返回每次捕获到的值 --这里有两个捕获列表,格式都是[^&=]+ 表示匹配所有非&和=格式的最长字符组 name = unescape(name) value = unescape(value) cgi[name] = value end end
做了解码,那么如何编码呢?就是照着URL格式的要求将某系特殊字符进行转换,首先转换特殊字符的编码函数:
e.g. --encoding function escape( s ) s = string.gsub(s,"[&=+%%%c]",function ( c ) --模式串匹配所有特殊字符 return string.format("%%%02X",string.byte(c)) --将字符转换成十六进制编码表示 end) s = string.gsub(s," ","+") --将所有的空格转换为'+' return s end
然后将table进行转换
<span style="font-size:18px;">e.g. --encode function encode( t ) local b = {} for k, v in pairs(t) do b[#b + 1] = (escape(k) .. "=" .. escape(v)) end return table.concat(b,"&") --以字符'&'连接所有key-value end</span>
Tab expansion
lua中空的捕获值()具有特殊含义,并不是说不做任何捕获,这个模式串捕获的是在源字符串中的位置?
e.g.
print(string.match("hello","()ll()")) -- 3 5
该结果与使用find得到的结果不同,因为第二个捕获到的是匹配到的位置的下一个,即5,是第二个'l'的下一个位置(第二个l的位置为4)。
一个很好的解释位置捕获的例子是将tab扩展为string?
<span style="font-size:18px;">e.g. --expand tab in a string function expandTabs( s,tab ) tab = tab or 8 --tab 的大小默认为 8 local corr = 0 s = string.gsub(s,"() ",function ( p ) local sp = tab - (p -1 + corr)%tab corr = corr - 1 + sp return string.rep(" ",sp) end) return s end</span>
函数中的gsub函数的模式串将会匹配string型变量中所有的tab键,捕获他们的位置。对于每个tab,替换函数使用这些位置来计算需要为这个tab留出多少空间。最后返回的s就已经计算好了要准备的空格。
使用示例:
print(expandTabs("x xx xx")) --打印出来的值为:x xx xx 间隔都是8个
这个函数的逆向操作,将一些空格转换为tab
e.g. function unexpandTabs( s,tab ) tab = tab or 8 s = expandTabs(s) local pat = string.rep(".",tab) s = string.gsub(s,pat,"%01") --将匹配到的tab(8个.)替换为一个标记 1 s = string.gsub(s," +1","1") --将多个空格和标记 替换为一个标记(意思应该是某个字符前有多个tab都将替换为一个tab) s = string.gsub(s,"1","") --最后将这些标记清除 return s end print(unexpandTabs(" x ")) -- 打印为x
21.6 Tricks of the Trade
在处理string型变量的时候模式匹配是非常有用的,你可以仅仅使用一个简单的string.gsub函数实现非常多的复杂操作。然而,正是因为这么强大,所以我们更需要小心翼翼。
写出正确的模式串是非常重要的,这关系到程序运行的效率。
一个匹配长字符串(有70个字符)的模式串
pattern = string.rep("^[^ ]",70) .. "[^ ]"
21.7 Unicode
string.reverse、string.byte、string.char、string.upper、string.lower不支持UTF-8编码格式的string型变量
string.format、string.rep可以支持UTF-8编码格式的string型变量(除了"%c"操作外),string.len、string.sub同样支持UTF-8编码格式的string型变量。
在模式匹配函数中,对UTF-8的支持程度决定于模式串的格式,字母组成的模式串是无问题的,字符类和字符组只支持ASCII格式的字符。
作者指出的一个缺陷:the function to convert between UTF-8 sequences and Unicode code points and to check the validity of UTF-8 string.缺少UTF-8 和 Unicode 格式编码之间的互相转换函数,和检测UTF-8格式字符串正确性的函数。