Qt中的正则表达式
RegExp
类使用正则表达式提供模式匹配。
正则表达式或“regexp”是用于匹配文本中的子串的模式。这在许多情况下都很有用,例如,
项 | 描述 |
---|---|
验证 | 正则表达式可以测试子字符串是否符合某些条件,例如:是一个整数或不包含空格。 |
搜索 | 正则表达式提供比简单子字符串匹配更强大的模式匹配,例如匹配mail, letter和correspondence中的一个,但不包括email, mailman, mailer, letterbox等单词。 |
搜索和替换 | 正则表达式可以用不同的子字符串替换所有出现的子字符串,例如,用&替换所有出现的&。除了&之前已经有&。 |
字符串拆分 | 正则表达式可用于识别字符串应分开的位置,例如,拆分制表符分隔的字符串。 |
简要介绍了正则,Qt的正则语言,一些例子以及函数文档本身的描述。 QRegExp以Perl的正则语言为模型。它完全支持Unicode。 QRegExp也可以用于更简单的通配符模式,类似于命令shell中的功能。可以使用setPatternSyntax()
更改QRegExp使用的语法规则。特别是,模式语法可以设置为QRegExp::FixedString
,这意味着要匹配的模式被解释为普通字符串,即特殊字符(例如反斜杠)不被转义。
关于正则表达式的优秀文本是由Jeffrey E.F.Friedl编写的正则表达式(第三版),ISBN 0-596-52812-4。
注意:在Qt 5中,新的QRegularExpression
类提供了正则表达式的Perl兼容实现,建议使用QRegExp
。
介绍
正则表达式是由表达式,量词和断言构建的。最简单的表达是一个字符,例如x或5.表达式也可以是用方括号括起来的一组字符。 [ABCD]将匹配A或B或C或D.我们可以将此表达式写为[A-D],并且匹配英语字母表中任何大写字母的表达式将写为[A-Z]。
量词指定必须匹配的表达式的出现次数。 x {1,1}表示匹配一个且只匹配一个x。 x {1,5}表示匹配包含至少一个x但不超过五个的x个字符序列。
请注意,通常,正则不能用于检查平衡括号或标记。例如,如果标签未嵌套,则可以编写正则表达式以匹配开始html 及其结束,但如果标签嵌套,则相同的正则表达式将匹配用错误的结束打开tags。对于片段boldbolder ,第一个将匹配第一个,这是不正确的。但是,可以编写正确匹配嵌套括号或标记的正则表达式,但前提是嵌套级别的数量是固定且已知的。如果嵌套级别的数量不固定且不知道数量,则无法编写正确的正则表达式。
假设我们想要一个正则表达式来匹配0到99范围内的整数。至少需要一个数字,所以我们从表达式[0-9] {1,1}开始,它只匹配一个数字一次。 此正则表达式匹配0到9范围内的整数。要匹配最大为99的整数,请将最大出现次数增加到2,因此正则表达式变为[0-9] {1,2}。 此正则表达式满足匹配从0到99的整数的原始要求,但它也将匹配出现在字符串中间的整数。 如果我们希望匹配的整数是整个字符串,我们必须使用锚断言,^
(插入符号)和(美元)。 当^
是正则表达式中的第一个字符时,表示正则表达式必须与字符串的开头匹配。 当$
是正则表达式的最后一个字符时,表示正则表达式必须与字符串的结尾匹配。 正则表达式变为^
[0-9] {1,2} $
。 请注意断言,例如 ^
和$
,不匹配字符。
如果您已经看过其他地方描述的正则表达式,它们可能与此处显示的不同。 这是因为一些字符集和一些量词是很常见,以至于它们被赋予特殊符号来表示它们。 [0-9]可以用符号\d
代替。 准确匹配一个出现的量词{1,1}
可以用表达式本身替换,即x{1,1}
与x相同。 所以我们的0到99匹配器可以写成^\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’的正则表达式开始。正则表达式是m {1,1} a {1,1} i {1,1} l {1,1},但由于字符表达式由{1,1}自动量化,我们可以简化mail,即’m’后跟’a’后跟’i’后跟’l’。现在我们可以使用竖线条|
,或者包括另外两个单词,因此我们用于匹配三个单词中的任何一个的正则表达式成为mail|letter|correspondence。匹配’mail’或’letter’或’correspondence’。虽然此正则表达式将匹配我们想要匹配的三个单词之一,但它也会匹配我们不想匹配的单词,例如“email”。为了防止正则表达式匹配不需要的单词,我们必须告诉它在单词边界处开始和结束匹配。首先,我们将正则表达式括放在括号中(mail|letter|correspondence)。括号将表达式组合在一起,它们标识了我们希望捕获的正则表达式的一部分。将表达式括在括号中允许我们将它用作更复杂的正则中的组件。它还允许我们检查实际匹配的三个单词中的哪一个。为了强制匹配开始和结束字边界,我们将正则表达式括在\b
字边界断言中:\b(mail|letter|correspondence)\b。现在正则表达式意味着:匹配一个单词边界,然后是括号中的正则表达式,后跟一个单词边界。 \b
断言匹配正则表达式中的位置,而不是字符。字边界是任何非字字符,例如,空格,换行符或字符串的开头或结尾。
如果我们想用HTML实体&替换&符号,匹配的正则表达式就是&。 但是这个正则表达式也将匹配已经转换为HTML实体的&符号。 我们想要仅替换后面没有跟amp;
的&
符号。 为此,我们需要负前瞻断言(?!__)
。 然后可以将正则表达式写为&(?!amp;)
,即匹配没有跟amp;
的&
符号。
如果我们想要计算字符串中所有’Eric’和’Eirik’的出现次数,那么两个有效的解决方案是\b(Eric|Eirik)\b
和\bEi?ri[ck]\b
。 需要使用单词边界断言'\b'
来避免匹配包含任一名称的单词,例如“Ericsson”。 请注意,第二个正则表达式匹配的更多:’Eric’,’Erik’,’Eiric’和’Eirik’。
上面讨论的一些示例在代码示例部分中实现。
字符和字符集的缩写
元素 | 描述 |
---|---|
c | 除非具有特殊的正则表达式含义,否则代表自身。 例如 c匹配字符c。 |
\c | 匹配字符,但下面指定的除外。 例如,要匹配字符串开头的文符,请写入^ 。 |
\a | 匹配ASCII铃声(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 (i.e., \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++编译器会在字符串中转换反斜杠。 要在正则表达式中包含\
,请输入两次,即\\
。 要匹配反斜杠字符本身,请输入四次,即\\\\
。
字符集
方括号表示匹配方括号中包含的任何字符。 上述字符集缩写可以出现在方括号中。 除字符集缩写和以下两个例外外,字符在方括号中没有特殊含义。
项 | 描述 |
---|---|
^ | 插入符号如果作为第一个字符出现(即紧接在开始方括号之后),则否定字符集。 [abc]匹配’a’或’b’或’c’,但[^abc] 匹配除“a”或“b”或“c”之外的任何内容。 |
- | 短划线表示一系列字符。 [W-Z] 匹配’W’或’X’或’Y’或’Z’。 |
使用预定义字符集缩写比使用跨平台和语言的字符范围更便携。 例如,[0-9]匹配西方字母表中的数字,但\d
匹配任何字母表中的数字。
注意:在其他正则表达式文档中,字符集通常称为“字符类”。
量词
默认情况下,表达式由{1,1}
自动量化,即它应该恰好出现一次。 在以下列表中,E
代表表达式。表达式是一个字符,或一组字符的缩写,或方括号中的一组字符,或括号中的表达式。
项 | 描述 |
---|---|
E? | 匹配零次或一次出现的E.此量词表示前一个表达式是可选的,因为它将匹配是否找到表达式。E? 与E {0,1}相同。 例如,凹痕? 匹配’凹痕’或’凹痕’。 |
E+ | 匹配一次或多次出现的E. E+ 与E {1,} 相同。 例如,0+ 匹配’0’,’00’,’000’等。 |
E* | 匹配零次或多次出现的E.它与E{0,} 相同。 * 量词通常会应用错误,其中应使用+ 。 例如,如果在表达式中使用\s*$ 来匹配以空格结尾的字符串,则它将匹配每个字符串,因为\s*$ 表示匹配零个或多个空格结尾的字符串。 正确的正则匹配至少有一个尾随空白字符的字符串是\s+$ 。 |
E{n} | 匹配恰好n次出现E. E {n} 与重复E n次相同。 例如,x{5} 与xxxxx相同。 它也与E{n,n} 相同,例如X{5,5} 。 |
E{n,} | 匹配至少n次出现的E. |
E{,m} | 匹配最多m次出现的E. E{,m} 与E{0,m} 相同。 |
E{n,m} | 匹配至少n次且最多m次出现的E. |
要将量词应用于整个字符串,请使用括号在表达式中将字符组合在一起。 例如,tag +
匹配’t’后跟’a’后跟至少一个’g’,而(tag)+
匹配至少一次'tag'
匹配。
注意:量词通常是“贪婪的”。 它们总是匹配尽可能多的文本。 例如,0+
匹配它找到的第一个零和第一个零之后的所有连续零。 适用于’20005’,它与’20005’相匹配。 量词可以变得非贪婪,请参阅setMinimal()。
捕获文本
括号允许我们将元素组合在一起,以便我们可以量化和捕获它们。例如,如果我们有与字符串匹配的表达式mail|letter|correspondence关系,我们知道其中一个单词匹配但不知道是哪一个。使用括号允许我们“捕获”其边界内匹配的任何内容,因此如果我们使用(mail | letter | Correspon)并将此正则与字符串“I sent you some email”相匹配,我们可以使用cap()
或capturedTexts( )
用于提取匹配字符的函数,在本例中为“mail”。
我们可以在正则本身中使用捕获的文本。为了引用捕获的文本,我们使用从1开始索引的反向引用,与cap()
相同。例如,我们可以使用\b(\w+)\W+\1\b
搜索字符串中的重复单词,这意味着匹配单词边界,后跟一个或多个单词字符,后跟一个或多个非单词字符,后跟相同的单词字符text作为第一个带括号的表达式,后跟一个单词边界。
如果我们想要将括号纯粹用于分组而不是用于捕获,我们可以使用非捕获语法,例如 (?:green|blue)
。非捕获括号开始'(?:'and end')'
。在这个例子中,我们匹配’green’或’blue’但我们不捕获匹配,所以我们只知道我们是否匹配但不知道我们实际找到的颜色。使用非捕获括号比使用捕获括号更有效,因为正则表达式引擎必须减少记录。
捕获和非捕获括号都可以嵌套。
由于历史原因,适用于捕获括号的量词(例如)比其他量词更“贪婪”。 例如,a (a *)将匹配“aaa”与cap(1)==“aaa”。 此行为与其他正则表达式引擎不同(特别是Perl)。 要获得更直观的捕获行为,请将QRegExp::RegExp2
指定给QRegExp
构造函数或调用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"]
断言
断言在正则表达式中出现的位置对语句进行了一些陈述,但它们与任何字符都不匹配。 在下面的列表中,E代表任何表达式。
项 | 描述 |
---|---|
^ | 插入符号表示字符串的开头。 如果你想匹配文字^ 你必须通过写\^ 来转义它。 例如,^#include 仅匹配以字符’#include’开头的字符串。 (当插入符号是字符集的第一个字符时,它具有特殊含义,请参见字符集。) |
$ | 表示字符串的结尾。 例如\d\s*$ 将匹配以数字结尾的字符串,可选地后跟空格。 如果你想匹配字符$ ,你必须通过写\\$ 来转义它。 |
\b | 一个单词边界。 例如,正则\bOK\b 意味着在单词边界(例如字符串或空格的开头)之后立即匹配字母“O”,然后紧接在另一个字边界之前的字母“K”(例如字符串或空格的结尾)。 但请注意,断言实际上并不匹配任何空格,所以如果我们写(\bOK\b )并且我们有匹配,它将只包含’OK’,即使该字符串是“It’s OK now”。 |
\B | 非字符边界。 如果\b 为假,则该断言为真。 例如,如果我们在“Left on”中搜索\Bon\B ,则匹配将失败(字符串的空格和结尾不是非字边界),但它将匹配“tonne”。 |
(?=E) | 如果表达式在正则中匹配,则此断言为true。 例如,const(?=\s+char) 只要跟随’char’就匹配’const’,如’static const char ‘。 (与const\s + char比较,它匹配’static const char ‘。) |
(?!E) | 如果表达式在正则表达式中此时不匹配,则此断言为真。 例如,const(?!\s+char )匹配’const’,除非它后跟’char’。 |
通配符匹配
大多数命令shell(如bash或cmd.exe)都支持“文件通配符”,即使用通配符标识一组文件的功能。setPatternSyntax()
函数用于在正则表达式和通配符模式之间切换。 通配符匹配比完整正则表达式简单得多,并且只有四个功能:
项 | 描述 |
---|---|
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
Perl用户注意事项
Perl支持的大多数字符类缩写都受QRegExp支持,请参阅字符和字符集的缩写。
在QRegExp中,除了字符类之外,^
始终表示字符串的开头,因此必须始终转义插入符号,除非用于此目的。 在Perl中,插入符的含义根据其发生的位置自动变化,因此很少需要它。 这同样适用于$
,在QRegExp中始终表示字符串的结尾。
QRegExp的量词与Perl的贪心量词相同(请参见上面的注释)。 非贪婪匹配不能应用于单个量词,但可以应用于模式中的所有量词。 例如,要匹配Perl regexp ro +?m
,需要:
QRegExp rx("ro+m");
rx.setMinimal(true);
Perl的/i
选项的等价物是setCaseSensitivity(Qt::CaseInsensitive)
。
可以使用循环模拟Perl的/g
选项。
在QRegExp中。匹配任何字符,因此所有QRegExp正则表达式都相当于Perl的/s
选项。 QRegExp没有等效于Perl的/m
选项,但可以通过各种方式进行模拟,例如将输入拆分为行或通过使用搜索换行符的正则表达式循环。
因为QRegExp是面向字符串的,所以没有\A
,\Z
或\z
断言。\G
断言不受支持,但可以循环模拟。
Perl的$&
是cap(0)
或capturedTexts()[0]
。 **$**,**$'**或**$+**没有QRegExp等价物。 Perl的捕获变量,$ 1,$ 2,...对应
cap(1)或
capturedTexts()[1],
cap(2)或
capturedTexts()[2]等。
QString::replace()
要替换模式,请使用。
/x`语法,也不支持指令,例如(?i)或regexp注释,例如(?#comment)。另一方面,C++的文字字符串规则可用于实现相同的目的:
不支持Perl的扩展
QRegExp mark("\\b" // word boundary
"[Mm]ark" // the word we want to match
);
使用与Perl相同的语法支持(?=pattern)
和(?!pattern)
。 Perl的lookbehind断言,“独立”子表达式和条件表达式不受支持。
还支持非捕获括号,具有相同的 (?:pattern)
语法。
有关Perl的拆分和连接函数的等价物,请参阅QString::split()
和QStringList::join()
。
注意:因为C++转换它们必须在代码中写入两次,例如\b
必须写成\\b
。
代码示例
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)
第三个字符串匹配’6’。 这是0到99范围内整数的简单验证正则表达式。
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)
第二个字符串匹配’This_is-OK’。我们使用字符集缩写'\S'
(非空格)和锚来匹配不包含空格的字符串。
在下面的示例中,我们匹配包含’mail’或’letter’或’Correspon’的字符串,但仅匹配整个单词,即不匹配’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()
函数重复匹配字符串中的正则表达式。 请注意,我们可以编写pos + = rx.matchedLength()
来跳过已经匹配的字符串,而不是一次向前移动一个字符。 计数将等于3,匹配’One Eric another Eirik, and an Ericsson. How many Eiriks, Eric?, 它与“Ericsson”或“Eiriks”不匹配,因为它们不受非字符边界的限制。
在正则中的一个常见用途是将分隔数据行分割为其组件字段。
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);
}
在此示例中,我们的输入行具有公司名称,网址和国家/地区的格式。 不幸的是,正则表达式相当长而且不是很通用 - 如果我们添加更多字段,代码将会中断。 一个更简单和更好的解决方案是在这种情况下查找分隔符'\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
由于其简单性,通配符匹配很方便的,但是可以使用完整的正则表达式来定义任何通配符正则表达式,例如.*.html
。
QRegExp可以使用setCaseSensitivity()
不区分大小写,并且可以使用非贪婪匹配,请参阅setMinimal()
。 默认情况下,QRegExp使用完整的正则表达式,但可以使用setPatternSyntax()
更改此值。 可以使用indexIn()
向前搜索,也可以使用lastIndexIn()
向后搜索。 可以使用captureTexts()
来访问捕获的文本,captureTexts()
返回所有捕获的字符串的字符串列表,或者使用cap()
返回给定索引的捕获字符串。 pos()
函数获取匹配索引并返回匹配所在的字符串中的位置(如果没有匹配则返回-1)。