string 学习3

来源:互联网 发布:解放战争诉苦大会 知乎 编辑:程序博客网 时间:2024/04/28 07:41

一、查找字符串的位置

i,j = string.find(str,"要查找的字符串"[,pos[, plain]]);

使用模式匹配查找字符串并返回起始位置(i),结束位置(j);
第三个参数pos指定搜索开始的位置,这个参数可以省略(使用默认值1);
第四个参数指定是否禁用模式匹配,默认为false;

如果查找失败,i,j都返回nil值.下面是一个简单的例子:

i,j = string.find("abcd" ,"cd");
 
if(i and j)then
    win.messageBox("在 'abcd' 中找到 'cd' ,从"..i.."到"..j);
end;
 
 
if(not string.find("abcd" ,"不可能") )then
    win.messageBox("没有从 'abcd' 中找到 '不可能' ");
end;

 

有时候我们可能不需要模式匹配,虽然他很好用。例如下面的代码

i,j = string.find("abc%d" ,"cd%d");-- 结果是找不到

我们要找的是%d,但是模式匹配认为%d是一个数字,所以我们要再加一个%取消转义

i,j = string.find("abc%d" ,"cd%%d");--终于找到了%d

我们可以写一个函数来自动的为所有标点添加%转义符取消转义

function string.plain(s)
    --%p匹配所有的标点,在前面加上%转义符取消转义成为普通的标记。
    --%%%1 其中%%表示%,%1表示查找模式中的第一个括号匹配的查找结果。
    return string.gsub(s, "(%p)", "%%%1");
end;
 
i,j = string.find("abc%d" ,string.plain("cd%d") );--终于找到了

一个更好更快的方法,是通过指定string.find的第四个参数plain为true禁用模式匹配。

i,j = string.find("abc%d" ,"cd%d",1,true);-- 终于找到了%d

string.find常常用来查找一个字符串在目标串中的位置,后面我们还会介绍更加强大的 string.match 以及 string.gmatch 函数

二、替换字符串

str = string.gsub (str, pattern, repl [, n])

参数分别为(目标字符串,查找模式字符串,替换字符串,替换次数)。
最后一个可选参数指定替换的次数,如果不指定则替换所有的找到的字符串。

--在模式匹配中 .圆点标记匹配任意字符。

str = string.gsub("abcd",".","k");
win.messageBox(str);-->结果是kkkk

str = string.gsub("abcd",".","k",1);
win.messageBox(str);-->结果是kbcd

pattern参数(查找模式字符串)可以使用所有模式匹配语法。
repl参数则为普通字符串,但是可以用%1,%2,%3..........等匹配模式匹配中的捕获(即一个括号中匹配的查找结果);
看下面的示例:


--在模式匹配中 .圆点标记匹配任意字符。

str = string.gsub("abcd","(.)","%1k");
win.messageBox(str);
-->结果是akbkckdk

二、模式匹配-语法

首先我们有必要解释一下什么是模式匹配。
模式匹配类似正则表达式,但更加简洁高效,更加易于学习、分析字符串的速度更快。

模式匹配:用特定的模式语法组成的模式串查找匹配有规则的字符串
模式串:根据模式语法编写的字符串,用来要找匹配有规则的目标字符串。

模式语法--字符类

. 任意字符
%a 字母
%c 控制字符
%d 数字
%l 小写字母
%p 标点字符
%s 空白符
%u 大写字母
%w 字母和数字
%x 十六进制数字
%z /0 字节码为0的字符,对于普通文本表示文本结束

上面字符类的大写形式表示小写所代表的集合的补集

%A 不是字母的字符
%C 不是控制字符的字符
%D 不是数字的字符
%L 不是小写字母的字符
%P 不是标点的字符
%S 不是空白符的字符
%U 不是大写字母的字符
%W 不是字母和数字的字符
%X 不是十六进制数字的字符
%Z 不是/0的字符

上面字符类的大写形式表示小写所代表的集合的补集

模式语法--自定义字符类

[]

方括号用来创建自定义的字符类,
例如[abcd]匹配一个可能是abcd其中之一的字符。[%w_]匹配数字、字母和下划线。
我们还可以在两个字符之间用连字符连接表示这两个字符之间范围内的字符集合。例如[0-7]等同于[01234567]。
我们还可以用字符集的开始处使用 `^′ 表示其补集,'[^0-7]' 匹配任何不是八进制数字的字符; '[^/n]' 匹配任何非换行符户的字符。

模式语法--转义符

%

后面如果是字母表示后面是一个字符类,
%后面如果是一个标点表示这个标点不是模式语法,例如%%表示本来的%

模式语法--修饰符列表

+ 匹配前一字符1次或多次,总是进行最长的匹配
* 匹配前一字符0次或多次,进行的是最长匹配
- 匹配前一字符0次或多次,进行的是最短匹配
? 匹配前一字符0次或1次

下面用一个简单的例子来说明最短匹配最长匹配的区别:

str = "a1234z"
 
str2 = string.match(str, "a%d-")
win.messageBox(str2); --[[因为-是最短匹配,显示"a",
因为"a"符合查找模式串的最短字符串]]
 
str2 = string.match(str, "a%d*")
win.messageBox(str2); --[[因为*是最短匹配,显示"a1234",
因为"a1234"符合查找模式串的最长字符串]]
 

str2 = string.match(str, "a%d-z")
win.messageBox(str2);--[[虽然-是最短匹配,
但是符合查找模式串的最短字符串是a1234d,因为模式串指定了数字后面有一个字符z]]

%b
用来匹配对称的字符,例如'%b()'匹配以`(′开始, 以 `)′结束的字符串:,例如'%b""'匹配以引号开始, 以引号结束的字符串:

str = 'a = "abcd"  '
 
str2 = string.match(str, '%b""')
win.messageBox(str2); --显示 "abcd"

字符串如果包含双引号则应放在单引号中,或者放在[[ ]] 中间,
如果放在双引号中则必须加上/转义符.

str =" a =/"abcd/" "; --正确
str =' a ="abcd" '; --正确
str =[[ a ="abcd" ]]; --正确

str =" a ="abcd"] "; --错误的写法


修饰符只能用来修饰单个字符或字符类。



模式语法--限定符


^


以`^′开头的模式只匹配目标串的开始部分


$

以`$′结尾的模式只匹配目标串的结尾部分

 

str = "1234"
 
if string.find(str, "^%d") then
    win.messageBox(str.." 字符串以数字开始")
end;
 
if string.find(str, "^[+-]?%d+$") then
    win.messageBox(str.." 字符串是一个整数")
end;

 

 

模式语法--匹配分组

( )

对查找结果进行分组.将模式串的一部份用圆括号括起来指定一个匹配分组,
每个匹配分组对应的查找结果称为一个捕获分组(capture)

我们可以在模式中使用'%d'(d代表1-9的数字) 表示第d个捕获分组。

三、模式匹配-分组捕获(capture)

1、在string.find中使用分组捕获,

string.find会将所有的分组捕获增加到返回值队列中。
我们用一个示例来解释一下.

下面是一个简单的例子,除了括号分组没有使用其他的模式语法。

i,j,v,v2 = string.find("abcd" ,"(ab)(cd)");
 
win.consoleOpen();
print(v,v2); --结果为v等于"ab" v2等于"cd"


再看一个类似的例子,使用了模式语法。

win.consoleOpen()
 
pair = "name = value"
 
--增加了两个返回值name,value,分别对应查找模式串中的两个括号组成的匹配分组
i, j, n, v = string.find(pair, "(%a+)%s*=%s*(%a+)")
print(i,j,n,v)  --> 显示 name value


2、向前引用


我们可以在模式中使用向前引用已经找到的捕获分组,'%d'(d代表1-9的数字) 表示第d个捕获的拷贝。

 

--假定有一个字符串
str =[[ a = "  test = 'abc' " ]]
 
--[[
现在的任务是用模式匹配找出  test = 'abc'

按照最基本的语法。
单引号开始的字符串必须用单引号结束,中间允许双引号。
双引号开始的字符串必须用双引号结束,中间允许单引号。
所以现在首要的问题是在模式串中向前引用第一次找到的引号。

而使用%b""又无法适用这种情况,因为字符串也有可能以单引号开始
 
]]
 
--第一个引号放到括号里形成一个匹配分组,其捕获结果用%1表示

--可以看到在模式串的后部份向前引用了%1
a, b, quote, str2 = string.find(str, [[(["'])(.-)%1]]);

win.consoleOpen();
print(str2)   -->显示 test = 'abc'
print(quote)  -->显示双引号
 

2、在string.gsub中使用分组捕获,

str = string.gsub (str, pattern, repl [, n])

参数分别为(目标字符串,查找模式串,替换字符串,替换次数)。
在模找模式串pattern中可以使用括号分组,在替换串参数repl中可以用%d(d为1到9的数字)引用分组捕获。

 
--复制字符串中的每一个字符.
str = string.gsub("hello","(.)","%1%1"); win.messageBox(str); --显示hheelllloo
 
--下面代码互换相邻的字符:
str = string.gsub("hello", "(.)(.)", "%2%1")
win.messageBox(str);--显示ehllo

3、在string.gsub中使用函数处理分组捕获,

str = string.gsub (str, pattern, func [, n])

string.gsub参数的第三个参数也可以是一个函数。
string.gsub会将所有分组捕获作为func的参数,并且调用func函数,并且用func函数的返回值作为替换串。

可以在模式匹配中直接使用函数,是模式匹配中最好用的一个功能。

--如果两个捕获都是数字则相加,否则将位置前后掉换
function func(a,b)
    if(tonumber(a) and tonumber(b) )then
        return a + b;--如果两个捕获都是数字则相加
    else
        return b..a;--否则将位置前后掉换
    end;  
end;
 
--复制字符串中的每一个字符.
str = string.gsub("abcdef23","(.)(.)",func);
win.messageBox(str); --显示badcef5

3、string.match 查找模式串

string.match(str, pattern)

第一个参数指定目标字符串,每二个参数指定查找模式串。这个函数与string.find很象。
但是不会返回查找结果的开始位置与结束位置。而仅仅是返回找到的一个或多个分组捕获。

local v,v2 = string.match("abcd", "(ab)(cd)");
 
win.consoleOpen();
print(v,v2); -->显示v等于"ab" v2等于"cd"

如果不显示的用括号指定捕获,函数将捕获整个匹配模式。

--%d表示数字 %d+表示最长匹配多个数字
local
v = string.match("abcd1234", "%d+");
 
win.consoleOpen();
print(v); -->显示v等于"1234"

3、string.gmatch 全局查找模式串

func = string.gmatch(str, pattern)

这个函数的功能很象string.gsub但是不会进行替换操作。

win.consoleOpen();
 
--for后面的变量对应查找模式串中的括号分组(有几对括号就有几个返回值)
for str,int in string.gmatch( "abcd1234xyz999","(%a+)(%d+)") do
   print(str,int);  
end;

string.gmatch函数比较适合用于泛型for循环。他可以遍历一个字符串内所有匹配模式的子串。
调用string.gmatch函数的时候,如果不显示的指定捕获,函数将捕获整个匹配模式。

实际上string.gmatch的返回值是一个迭代器函数,每调用这个函数一次就会向后查找一个匹配直到查找失败。
上面在泛型for中执行string.gmatch的过程实际上可以理解成这样:

win.consoleOpen();
 
f = string.gmatch( "abcd1234xyz999","(%a+)(%d+)");
 
str,int = f();
print(str,int);
 
str,int = f();
print(str,int);

四、自动提取中文字符串

str = [[
abcddddddddddddd这是一个中文字符串adfasdfasdf这又是一个中文字符串<a href="">这是超链接标题</a>
 
]]
 
--现在需要自动的把上面的中文提取出来并且放到一个数组里,
--不要英文不要空格只要中文字
--怎么办呢?
 
--打开控制台
win.consoleOpen();
 
for str in string.gfind(str,"([/xA1-/xF7][/xA1-/xF7/xA1-/xFE]+)") do
   print(str)
end
 
--[[
一个中文字实际上由两个字节组成,常用字的编码范围是有规律的,
第一个字节是在0xA1-0xF7之间,第二个字节是在 0xA1-0xFE 之间。
字符串里面 /xFE表示后面是一个十六进制 ,如果用 /254 后面跟三位十进制数字也可以。
 
模式匹配中 [] 表示一个字符范围,例如[0-9]表示所有数字 [0-9a-zA-Z]表示所有数字也英文字母
+表示任意多个连续的字符匹配。
 
一组括号表示添加一个返回值,这里只有一组括号,所以只有一个返回值 str。
 
因为中文字至少是两个字节,所以首先用[/xA1-/xF7]检查每一个字节是否中文字的首字节。
如果发现两个字节都符合中文字的编码规则,则向后长匹配所有在中文编码范围的字节。
]]

五、模式匹配-查找网页源代码中的超链接

下面是一个应用模式匹配的完整示例 - 下载网页并取得网页源代码中的所有超链接

win.consoleOpen() --打开控制台窗口
print("请稍候.......")
 
--[==============================[
大家知道,在web窗体中,我们可以用 wb:eleLinks() 得到所有的链接链(返回一组element,详见教程)
这需要我们用web窗体打开网页,然后再调用 wb:eleLinks()
那么,我们有没有更好的办法直接分析源代码,并取得所有的超链接呢?
有的,如下:
--]==============================]
 

-- 首先,我们下载 www.yhhe.net 的主页到一个字符串(string对象)中
 
strHtmlCode = web.getURL("http://www.yhhe.net")
 
--然后我们创建一个空的table数组
links = {}
 
 
--[==============================[
string.gmatch 全局查找子串,每个括号指定的一个分组匹配(对应一个分组捕获,在泛型for中增加一个返回值)
与 string.match 不同,string.gmatch发现一个完整匹配以后,会继续向后查找,
--]==============================]
for href in string.gmatch(strHtmlCode, "%s*href%s*=%s*/"?/'?([:%w%./@]+)/"?/'?%s*") do
    table.insert(links, href) --发现一个超链接,添加到links列表中
end
--[==============================[
我们解释一下模式串 "%s*href%s*=%s*/"?/'?([:%w%./@]+)/"?/'?%s*"
%是转义符 %s表示空白字符 %s*表示空白字符出现一次或多次 /"表示引号(/转义符的使用与普通字符串相同)
?表示出现一次或零次 %w表示字母和数字 %.表示.(因为.是特殊字符,所以需要%还原)
[]是一个自定义的字符类,如[0-9]匹配所有数字
+是修饰符表示前面的字符出现一次或多次

--]==============================]
 
local url;

--好了,我们现在得到了所有的超链接,并保存在links列表中了,我们输出看一下
for i,link in pairs(links) do
    print("发现一个超链接:"..link)
    url = urlparse.new(link);
    print("","主机",url:host() );
    print("","路径",url:path() );
    print("","协议",url:scheme() );
end;
 

六、模式匹配-应用技巧

1、限制严格的模式比一个限制宽松的模式更快

2、严格限制模式匹配的开始字符可以显著提升效率.

例如模式串"(.-)test" 用来获取test以前的全部字符
上面的算法从目标串的第一个字符开始匹配直到字符串结束,
如果没有找到,则从目标串的第二个字符开始再次查找。
这样的查找效率是很低的。

解决办法是加上限定符"^(.-)test" 限定仅与目标串的开始字符匹配。

3、不要使用可能匹配空字符串的模式。
例如"%a*",因为可以出现零次则空字符串也是符合条件的。
永远不要写一个以`-′开头或者结尾的模式,因为它将匹配空字符串。

4、使用 string.rep函数构造重复指定的次数的模式。

例如 pattern = string.rep("%d", 10);
实际生成的 pattern 等于 "%d%d%d%d%d%d%d%d%d%d"
这个模式串匹配10个连续的数字。

原创粉丝点击