Qt扫盲篇之QRegExp正则匹配类总结
作者:太阳风暴
一、正则表达式概述
正则表达式或“regexp”是一种用于匹配文本中的子字符串的模式。正则表达式在字符串处理的时候发挥重要的作用,用途如下四种:
- 验证
regexp可以测试子字符串是否满足某些条件,例如是整数或不包含空格。 - 搜索
regexp提供了比简单的子字符串匹配更强大的模式匹配,例如,匹配单词 mail、letter 或 correspondence 中的一个,但不匹配单词 email、mailman、mailer、letterbox 等。 - 搜索和替换
regexp可以用不同的子字符串替换所有出现的子字符串,例如,将所有出现的&替换为&除非&后面已经跟了一个amp; - 字符串分割
regexp可以用来标识字符串应该在哪里被分割,例如分割制表符分隔的字符串。
推荐看下面链接关于正则表达的教程,我们只需要知道在Qt里怎么调用这个 api 就行啦,这个网站还有在线的测试正则表达式的工具,验证自己的正则表达正确与否
http://edu.jb51.net/regexp/regexp-tutorial.html
本文将简要介绍regexp,描述Qt的regexp语言、一些示例以及函数文档本身。QRegExp是基于Perl的 regexp 语言建模的。它完全支持Unicode。QRegExp还可以以一种更简单的通配符模式使用,这种模式类似于命令shell中的功能。QRegExp使用的语法规则可以通过 setPatternSyntax() 改变。特别地,模式语法可以设置为 QRegExp::FixedString,这意味着要匹配的模式被解释为普通字符串,即特殊字符(例如反斜杠)不会转义。
- 注意:在Qt 5中,新的QRegularExpression类提供了一个与Perl兼容的正则表达式实现,建议用它来代替QRegExp。
二、正则匹配介绍
正则表达式由表达式、量词和断言组成。 最简单的表达式是一个字符,例如x或5。表达式也可以是方括号中包含的一组字符。[ABCD]将匹配A、B、C或D。我们可以将这个表达式写成[A-D],而匹配任何英文大写字母的表达式写成[A- Z]。
量词指定了必须匹配的表达式出现的次数。X{1,1}表示匹配1个且仅匹配1个; X{1,5}表示匹配一个包含至少一个X但不超过5个X的序列。
注意,一般来说,正则表达式不能用于检查平衡的括号或标签。例如,可以编写一个 regexp 来匹配 html的开头<b>和结尾</b>,如果<b>标签没有嵌套,但如果<b>标签嵌套,相同的regexp将匹配一个开头<b>标签和错误的结尾。对于片段<b>bold <b>bold </b></b>,第一个<b>与第一个匹配,这是不正确的。不过,可以编写一个regexp来正确匹配嵌套的方括号或标签,但前提是嵌套级别是固定且已知的。如果嵌套级别不是固定的且已知的,那么就不可能写出一个不会失败的regexp。
假设我们想要一个 regexp 来匹配 0 到 99 之间的整数。至少需要一位数字,因此我们从表达式 [0-9]{1,1} 开始,它只匹配一位数字一次。这个 regexp 匹配 0 到 9 之间的整数。要匹配最多 99 个整数,将最大出现次数增加为 2,因此 regexp 变为 [0-9]{1,2}。这个 regexp 满足匹配 0 到 99 之间的整数的原始要求,但它也匹配出现在字符串中间的整数。
如果希望匹配的整数是整个字符串,就必须使用锚点断言,^ (上三角父)和 $ (美元符)。当^是regexp中的第一个字符时,这意味着regexp必须从字符串的开始匹配。当 $ 是regexp的最后一个字符时,这意味着regexp必须匹配字符串的结尾。regexp变为 ^ [0-9]{1,2}$。
注意,断言,例如^和$,不匹配字符,而是匹配字符串中的位置。
如果您在其他地方看到过描述的正则表达式,它们可能与这里显示的不同。这是因为某些字符集和量词太常见了,以至于它们被赋予了特殊的符号来表示。
[0-9]可以用符号\d代替。只匹配一次出现的量词 {1,1} 可以用表达式本身替换,即 x{1,1} 与 x 相同。因此,我们的 0 到 99 的matcher可以写成 ^\d{1,2}$ , 它也可以写成 ^\d\d{0,1}$ ,即从字符串的开始匹配一个数字,紧接着是0或1个数字。实际上,它可以写成 ^\d\d?$ 的吗? 这也是量词{0,1}的简写,即出现0次或1次吗?使表达式成为可选的。
^\d\d?$ 表示从字符串的开始,匹配一个数字,紧接着是 0 或 1 个数字,接着是字符串的结束。
要编写一个匹配单词’mail’、‘letter’或’correspondence’,但不匹配包含这些单词的单词,例如 ‘email’、‘mailman’、‘mailer’ 和 ‘letterbox’,请从匹配 ‘mail’ 的 regexp 开始。完整地说,regexp 是 m{1,1}a{1,1}i{1,1}l{1,1},但是因为字符表达式是由{1,1}自动量化的,所以我们可以将 regexp 简化为 mail,即 ‘m’、‘a’、‘i’、‘l’。现在我们可以使用竖线 | (表示or)来包含另外两个单词,因此匹配这三个单词中的任何一个的regexp就变成了 mail|letter|correspondence。匹配 ‘mail’、‘letter’或’correspondence’。
虽然这个regexp将匹配我们想要匹配的三个单词中的一个,但它也会匹配我们不想匹配的单词,例如’email’。为了防止regexp匹配不需要的单词,我们必须告诉它在单词边界处开始和结束匹配。首先,我们将regexp括在括号中(mail|letter|correspondence)。
括号将表达式组合在一起,它们标识了我们想要捕获的regexp部分。将表达式放在括号中允许我们在更复杂的regexp中将其作为组件使用。 它还允许我们检查三个单词中哪个是真正匹配的。为了强制匹配在单词边界开始和结束,我们将regexp包含在 \b 单词边界断言中: \b(mail|letter|correspondence)\b 现在,regexp的意思是:匹配一个单词边界,然后是放在括号中的regexp,最后是单词边界。\b断言匹配regexp中的位置,而不是字符。
单词边界是任何非单词字符,例如空格、换行符或字符串的开头和结尾。
如果我们想将&字符替换为HTML实体&,则匹配的regexp就是&。但这个regexp也会匹配&符号
三、表达式字符
字符 | 含义 |
---|---|
c | 字符表示自身,除非它有特殊的regexp含义。例如,c匹配字符c。 |
\c | 反斜杠后面的字符匹配字符本身,下面指定的字符除外。例如,要匹配字符串开头的插入字符,可以写^。 |
\a | 匹配ASCII的bell码(BEL, 0x07)。 |
\f | 匹配ASCII表单提要(FF, 0x0C)。 |
\n | 匹配ASCII换行符(LF, 0x0A, Unix换行符)。 |
\r | 匹配ASCII回车(CR, 0x0D)。 |
\t | 匹配ASCII水平制表符(HT, 0x09)。 |
\v | 匹配ASCII垂直选项卡(VT, 0x0B)。 |
\xhhhh | 匹配十六进制数hhhh(在0x0000和0xFFFF之间)对应的Unicode字符。 |
\0ooo(例:\zero ooo) | 匹配八进制数ooo的ASCII/Latin1字符(0到0377之间)。 |
. (dot) | 匹配任意字符(包括换行符)。 |
\d | 匹配一个数字(QChar::isDigit())。 |
\D | 匹配非数字。 |
\s | 匹配空白字符(QChar::isSpace())。 |
\S | 匹配非空白字符。 |
\w | 匹配单词字符(QChar::isLetterOrNumber()、QChar::isMark()或’_')。 |
\W | 匹配非单词字符。 |
\n | 第n个后向引用,例如\1、\2等 |
注意:c++编译器会转换字符串中的反斜杠。要在regexp中包含\,请输入两次,即 \\。要匹配反斜杠字符本身,请输入四次,即 \\\\。
四、字符集
中括号表示匹配中包含的任意字符。上面描述的字符集缩写可以出现在方括号中的字符集中。除了字符集缩写和以下两个例外,方括号中的字符没有特殊含义。
- ^ :如果字符集作为第一个字符出现(即紧跟在左方括号之后),则插入符号对它取反。[abc]匹配’a’、‘b’或’c’,但[^abc]匹配除’a’、'b’或’c’以外的任何值。
- - :破折号表示一个字符范围。[W-Z]匹配’W’或’X’或’Y’或’Z’。
使用预定义的字符集缩写比跨平台和语言使用字符范围具有更好的可移植性。 例如,[0-9]匹配西方字母表中的一个数字,而 \d 匹配任何字母表中的一个数字。
注意:在其他regexp文档中,字符集通常称为“字符类”。
五、量词
默认情况下,一个表达式会自动被{1,1}量化,即它应该只出现一次。在下面的列表中,E代表表达式。表达式是一个字符,或一组字符的缩写,或方括号中的一组字符,或括号中的表达式。
符号 | 含义 |
---|---|
E? | 匹配0或1次e。这个量词意味着前一个表达式是可选的,因为无论是否找到前一个表达式,它都会匹配。E ?和E{0,1}一样。例如,凹痕?匹配’dent’或’ '。 |
E+ | 匹配一次或多次E。E+与E{1,}相同。例如,0+匹配0、00、000等。 |
E* | 匹配零次或多次E。它与E{0,}相同。量词经常在错误中使用,而应该使用+。例如,如果在表达式中使用\sKaTeX parse error: Undefined control sequence: \s at position 26: …符串,它将匹配每个字符串,因为\̲s̲*意味着匹配零个或多个空格,然后是字符串结尾。要匹配结尾至少有一个空格的字符串,正确的regexp是\s+$。 |
E{n} | 完全匹配E n次E{n}等价于重复E n次例如,x{5}表示xxxxx。它也与E{n,n}相同,例如x{5,5}。 |
E{n} | 至少匹配n次E。 |
E{, m} | E最多匹配m次。E{,m}与E{0,m}相同。 |
E{n, m} | E至少出现n次,最多出现m次。 |
如果要将量词应用于前面的字符以外的字符,可以使用括号将字符组合在一个表达式中。例如,tag+匹配的是’t’和’a’以及至少一个’g’,而(tag)+匹配的是’tag’的至少一次出现。
- 注意:量词通常是“贪婪的”。它们总是匹配尽可能多的文本。 例如,0+匹配它找到的第一个0以及第一个0之后的所有连续的0。应用到20005,它匹配20005。量词可以变成非贪心的,参见setMinimal()。
六、断言
断言对出现在regexp中的文本做了一些声明,但它们不匹配任何字符。在下面的列表中,E代表任何表达式。
符号 | 含义 |
---|---|
^ | 脱字符表示字符串的开始。如果你想匹配字面量^ ,就必须将其转义为 \^ 。例如,^#include只匹配以’#include’开头的字符串。(当插入符号是字符集的第一个字符时,它有特殊的含义,请参见“字符集”。) |
$ | 美元表示字符串的结束。例如\d\s*$将匹配以数字结尾的字符串(可选地,数字后面有空格)。如果你想匹配字面上的 $ ,就必须转义为\$ 。 |
\b | 单词边界。例如,regexp \bOK\b表示匹配紧跟在单词边界(例如字符串或空格的开始)之后的字母’O’,然后匹配紧跟在另一个单词边界(例如字符串或空格的结束)之前的字母’K’。但是请注意,断言实际上并不匹配任何空格,所以如果我们写(\bOK\b)并且我们有一个匹配,它将只包含’OK’,即使字符串是" it 's OK now"。 |
\B | 非单词边界。当\b为false时,该断言为true。例如,如果我们在"Left on"中搜索\Bon\B,匹配将失败(空格和字符串末尾不是非单词边界),但在"公吨"中会匹配。 |
(?=E) | 积极的超前。如果表达式在regexp中的这一点匹配,则此断言为true。例如,const(?=\s+char)匹配const ,只要它后面跟着char ,比如static const char * 。(与匹配’static const char *'的const\s+char比较。) |
(?!E) | 消极的超前。如果表达式在regexp中的此时不匹配,则此断言为true。例如,const(?!\s+char)匹配const ,除非它后面跟着char 。 |
七、通配符匹配
大多数命令shell,如bash或cmd.exe,都支持“文件通配符”,即通过使用通配符来识别一组文件的能力。setPatternSyntax()函数用于在regexp和通配符模式之间切换。通配符匹配比完整的正则表达式简单得多,只有4个特征。
符号 | 含义 |
---|---|
c | 除下面提到的字符外,任何字符都代表自己。因此c匹配字符c。 |
? | 匹配任意单个字符。它和。完整的正则表达式。 |
* | 匹配零个或多个任意字符。在完整的正则表达式中,它等同于 .* |
[…] | 字符集可以用方括号表示,类似于完整的正则表达式。在字符类内部,就像在字符类外部一样,反斜杠没有特殊的含义。 |
在模式通配符中,通配符不能转义。在WildcardUnix模式中,字符 ‘\’ 对通配符进行转义。
例如,如果我们使用通配符模式,并且有包含文件名的字符串,我们可以使用*. HTML来标识HTML文件。这将匹配零个或多个字符,然后是一个点号,然后是h
、t
、m
和l
。
要用通配符表达式测试字符串,可以使用exactMatch()。例如:
QRegExp rx("*.txt"); rx.setPatternSyntax(QRegExp::Wildcard); rx.exactMatch("README.txt"); // returns true rx.exactMatch("welcome.txt.bak"); // returns false
八、获取文本
括号允许我们将元素组合在一起,以便我们可以量化和捕获它们。例如,如果我们有表达式mail|letter|correspondence,它匹配一个字符串,我们知道其中一个单词匹配,但不知道是哪个单词。使用括号允许我们“捕获”在它们的范围内匹配的任何内容,因此如果我们使用(mail|letter|correspondence)并将这个regexp与字符串“I sent you some email”匹配,我们可以使用cap()或capturedTexts()函数来提取匹配的字符,在本例中是’mail’。
我们可以在regexp中使用捕获的文本。为了引用捕获的文本,我们使用从1开始索引的后向引用,与cap()相同。例如,我们可以使用\b(\w+)\ w+ \1\b在字符串中搜索重复的单词,这意味着匹配单词边界,然后是一个或多个单词字符,然后是一个或多个非单词字符,然后是与第一个括号表达式相同的文本,然后是单词边界。
如果我们想使用圆括号纯粹用于分组而不是捕获,我们可以使用非捕获语法,例如(?:green|blue)。非捕获性括号以(?:
and end )
。在这个例子中,我们匹配green
或blue
,但我们不捕获匹配,因此我们只知道是否匹配,而不知道实际找到的颜色。使用非捕获式括号比使用捕获式括号更高效,因为regexp引擎需要做的簿记工作更少。
捕获括号和非捕获括号都可以嵌套。
由于历史原因,用于捕获括号的量词(例如*)比其他量词更“贪婪”。例如,a*(a*)将匹配"aaa"与cap(1) == “aaa”。这种行为不同于其他regexp引擎(特别是Perl)。为了获得更直观的捕获行为,可以在QRegExp构造函数中指定QRegExp::RegExp2,或者调用setPatternSyntax(QRegExp::RegExp2)。
如果无法事先确定匹配项的数量,一种常见的习惯用法是在循环中使用cap()。例如:
QRegExp rx("(\\d+)"); QString str = "Offsets: 12 14 99 231 7"; QStringList list; int pos = 0; while ((pos = rx.indexIn(str, pos)) != -1) { list << rx.cap(1); pos += rx.matchedLength(); } // list: ["12", "14", "99", "231", "7"]
九、例子
第三个字符串匹配’6’。这是一个简单的验证0到99范围内整数的regexp。
QRegExp rx("^\\d\\d?$"); // match integers 0 to 99 rx.indexIn("123"); // returns -1 (no match) rx.indexIn("-6"); // returns -1 (no match) rx.indexIn("6"); // returns 0 (matched at position 0)
第二个字符串与This_is-OK
匹配。我们使用了字符集缩写’\S’(非空白)和锚点来匹配不包含空白的字符串。
QRegExp rx("^\\S+$"); // match strings without whitespace rx.indexIn("Hello world"); // returns -1 (no match) rx.indexIn("This_is-OK"); // returns 0 (matched at position 0)
在下面的例子中,我们匹配包含’mail’或’letter’或’correspondence’的字符串,但只匹配整个单词,即不匹配’email’
QRegExp rx("\\b(mail|letter|correspondence)\\b"); rx.indexIn("I sent you an email"); // returns -1 (no match) rx.indexIn("Please write the letter"); // returns 17
第二个字符串匹配"Please write The letter"。单词“letter”也被捕获了(因为有括号)。我们可以这样查看我们捕获了什么文本:
QString captured = rx.cap(1); // captured == "letter"
这将从第一组捕获括号中捕获文本(从左到右计算捕获的左括号)。括号从1开始计数,因为cap(0)是整个匹配的regexp(相当于大多数regexp引擎中的’&')。
QRegExp rx("&(?!amp;)"); // match ampersands but not & QString line1 = "This & that"; line1.replace(rx, "&"); // line1 == "This & that" QString line2 = "His & hers & theirs"; line2.replace(rx, "&"); // line2 == "His & hers & theirs"
在这里,我们将QRegExp传递给QString的replace()函数,以将匹配的文本替换为新的文本。
QString str = "One Eric another Eirik, and an Ericsson. " "How many Eiriks, Eric?"; QRegExp rx("\\b(Eric|Eirik)\\b"); // match Eric or Eirik int pos = 0; // where we are in the string int count = 0; // how many Eric and Eirik's we've counted while (pos >= 0) { pos = rx.indexIn(str, pos); if (pos >= 0) { ++pos; // move along in str ++count; // count our Eric or Eirik } }
我们使用了indexIn()函数来重复匹配字符串中的regexp。请注意,我们可以编写pos+ = rx.matchedLength()来跳过已经匹配的字符串,而不是每次前进一个字符pos++。计数将等于3,匹配’一个Eric另一个erik,和一个Ericsson。埃里克,有多少个eirik ?”它不匹配Ericsson
或Eiriks
,因为它们没有非单词的边界。
regexp的一个常见用途是将带分隔符的数据行分割到它们的组件字段中。
str = "The Qt Company Ltd\tqt.io\tFinland"; QString company, web, country; rx.setPattern("^([^\t]+)\t([^\t]+)\t([^\t]+)$"); if (rx.indexIn(str) != -1) { company = rx.cap(1); web = rx.cap(2); country = rx.cap(3); }
在这个例子中,我们的输入行格式为公司名称、网站地址和国家。不幸的是,regexp相当长,并且不太通用——如果我们添加更多字段,代码将中断。一个更简单、更好的解决方案是查找分隔符(在本例中是’\t’),并获取其周围的文本。QString::split()函数可以接受分隔符字符串或regexp作为参数,并相应地分割字符串。
QStringList field = str.split("\t");
这里[0]字段表示公司,[1]字段表示网站地址,等等。
为了模仿shell的匹配,我们可以使用通配符模式。
QRegExp rx("*.html"); rx.setPatternSyntax(QRegExp::Wildcard); rx.exactMatch("index.html"); // returns true rx.exactMatch("default.htm"); // returns false rx.exactMatch("readme.txt"); // returns false
通配符匹配很方便,因为它很简单,但任何通配符regexp都可以用完整的regexp来定义,例如*.html$ ,注意,我们不能用通配符同时匹配 .html 和 .htm 文件,除非我们使用 *.htm* ,它也会匹配test.html.bak
。一个完整的regexp可以提供我们需要的精度: .*.html?$
QRegExp可以使用setcasessensitivity()不区分大小写进行匹配,也可以使用非贪婪匹配,参见setMinimal()。默认情况下,QRegExp使用完整的regexp,但这可以用setPatternSyntax()改变。使用indexxin()可以实现正向搜索,使用lastindexxin()可以实现反向搜索。捕获的文本可以使用capturedTexts()来访问,它会返回所有捕获的字符串的字符串列表,或者使用cap()来返回给定索引的捕获的字符串。pos()函数接受一个匹配项索引,并返回匹配项在字符串中的位置(如果没有匹配项,则返回-1)。
总结
到此这篇关于Qt扫盲篇之QRegExp正则匹配类的文章就介绍到这了,更多相关Qt QRegExp正则匹配类内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!