正则表达式由表达式、量词和断言构成。最简单的表达式是一个字符,例如 x 或 5。表达式还可以是方括号中的一组字符。[ABCD] 将匹配 A、B、C 或 D。我们可以将这个表达式写成 [A-D],匹配英文字母表中的任何大写字母的表达式可以写成 [A-Z]。
量词指定必须匹配表达式的出现次数。x{1,1} 表示匹配一个且仅一个 x。x{1,5} 表示匹配一个包含至少一个 x 但不超过五个 x 的字符序列。
请注意,通常情况下,正则表达式不能用于检查平衡的括号或标签。例如,可以编写一个正则表达式来匹配一个开启的 HTML 标签和它的闭合
标签,但如果
标签是嵌套的,同样的正则表达式将匹配错误的开启
标签和闭合
标签。对于
bold bolder
这个片段,第一个 会与第一个
匹配,这是不正确的。然而,可以编写一个正则表达式来正确匹配嵌套的括号或标签,但前提是嵌套层级的数量是固定且已知的。如果嵌套层级的数量不是固定且已知的,那么就不可能编写一个不会出错的正则表达式。
假设我们想要一个正则表达式来匹配范围在 0 到 99 的整数。至少需要一个数字,所以我们从表达式 [0-9]{1,1} 开始,它表示精确匹配一个数字。这个正则表达式匹配范围在 0 到 9 的整数。为了匹配范围在 0 到 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'
。
方括号表示匹配方括号中包含的任意字符。上面描述的字符集缩写可以出现在方括号中的字符集中。除了字符集缩写和以下两个例外情况,方括号中的字符没有特殊含义。
字符 | 作用 | 解释 |
---|---|---|
^ | The caret negates the character set if it occurs as the first character (i.e. immediately after the opening square bracket). [abc] matches ‘a’ or ‘b’ or ‘c’, but [^abc] matches anything but ‘a’ or ‘b’ or ‘c’. | 如果插入符号作为第一个字符出现(即紧接在开始的方括号之后),则对字符集取反值。[abc]匹配’a’或’b’或’c’,但[^abc]匹配’a’或’b’或’c’以外的任何东西。 |
- | The dash indicates a range of characters. [W-Z] matches ‘W’ or ‘X’ or ‘Y’ or ‘Z’. | 破折号表示一个字符范围。[W-Z]匹配’W’或’X’或’Y’或’Z’。 |
使用预定义的字符集缩写比跨平台和语言使用字符范围更具可移植性。例如,[0-9]匹配西方字母中的一个数字,但\d匹配任何字母中的一个数字。
注意:在其他regexp文档中,字符集通常被称为“字符类”。
QRegExp
类提供了使用正则表达式进行模式匹配的功能。正则表达式是用于在文本中匹配子字符串的模式。在许多情况下,这非常有用,例如:
mail
、letter
或correspondence
中的一个,但不匹配email
、mailman
、mailer
、letterbox
等单词。&
替换为&
,除非&
后面已经跟着amp;
。QRegExp
基于Perl
的正则表达式语言,并完全支持Unicode
。QRegExp
还可以以更简单的通配符模式使用,类似于命令行中的功能。可以使用setPatternSyntax()
函数更改QRegExp
使用的语法规则。特别地,可以将模式语法设置为QRegExp::FixedString
,这意味着要匹配的模式被解释为普通字符串,即不转义特殊字符(例如反斜杠)。
注意
:在Qt 5
中,新的QRegularExpression
类提供了与Perl
兼容的正则表达式实现,并建议使用它来替代QRegExp
。
构造函数:
析构函数:
方法:
// integers 1 to 9999 :在 1 - 9999 区间
QRegExp rx("[1-9]\\d{0,3}");
// the validator treats the regexp as "^[1-9]\\d{0,3}$" : [1-9]\\d{0,3} = ^[1-9]\\d{0,3}$
QRegExpValidator v(rx, 0);
QString s;
int pos = 0;
s = "0";
qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << v.validate(s, pos); // returns Invalid
s = "12345";
qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << v.validate(s, pos); // returns Invalid
s = "1";
qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << v.validate(s, pos); // returns Acceptable
rx.setPattern("\\S+"); // one or more non-whitespace characters : 一个或多个非空白字符
v.setRegExp(rx);
s = "myfile.txt";
qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << v.validate(s, pos); // Returns Acceptable
s = "my file.txt";
qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << v.validate(s, pos); // Returns Invalid
// A, B or C followed by exactly five digits followed by W, X, Y or Z:A、B或C后面正好跟着五位数字,后面跟着W、X、Y或Z
rx.setPattern("[A-C]\\d{5}[W-Z]");
v.setRegExp(rx);
s = "a12345Z";
qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << v.validate(s, pos); // Returns Invalid
s = "A12345Z";
qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << v.validate(s, pos); // Returns Acceptable
s = "B12";
qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << v.validate(s, pos); // Returns Intermediate
// match most 'readme' files:匹配更多的 'readme' 文本
rx.setPattern("read\\S?me(\.(txt|asc|1st))?");
rx.setCaseSensitivity(Qt::CaseInsensitive);
v.setRegExp(rx);
s = "readme";
qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << v.validate(s, pos); // Returns Acceptable
s = "README.1ST";
qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << v.validate(s, pos); // Returns Acceptable
s = "read me.txt";
qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << v.validate(s, pos); // Returns Invalid
s = "readm";
qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << v.validate(s, pos); // Returns Intermediate
QRegExp reg;
reg.setPattern("^[0-9]*$");
ui->lineEdit->setValidator(new QRegExpValidator(reg));
注意
:在Qt 5
中,新的QRegularExpression
类提供了与Perl
兼容的正则表达式实现,并建议使用它来替代QRegExp
。
QRegularExpression
是一个实现了与Perl
兼容的正则表达式的类。它完全支持 Unicode
。
正则表达式由两个部分组成:模式和一组模式选项,这些选项可以改变模式字符串的含义。
您可以通过将字符串传递给QRegularExpression
构造函数来设置模式字符串:
QRegularExpression re("a pattern");
这将把模式字符串设置为指定的模式。您还可以使用 setPattern()
函数在现有的 QRegularExpression
对象上设置模式:
QRegularExpression re;
re.setPattern("another pattern");
请注意,由于 C++ 字符串字面量的规则,您必须使用另一个反斜杠来转义模式字符串中的所有反斜杠:
// 匹配两个数字后跟一个空格和一个单词
QRegularExpression re("\\d\\d \\w+");
// 匹配一个反斜杠
QRegularExpression re2("\\\\");
pattern()
函数返回当前设置为 QRegularExpression
对象的模式字符串:
QRegularExpression re("a third pattern");
QString pattern = re.pattern(); // pattern == "a third pattern"
二者语法一样,下面只介绍QRegularExpression
用法。
若要进行匹配,只需调用 match()
函数并传递一个字符串进行匹配。我们将这个字符串称为主题字符串
。match()
函数的结果是一个 QRegularExpressionMatch 对象
,可用于检查匹配的结果。例如:
// 匹配两个后跟一个空格和一个单词
QRegularExpression re("\\d\\d \\w+");
QRegularExpressionMatch match = re.match("abc123 def");
bool hasMatch = match.hasMatch(); // true
如果匹成功,隐式捕获组编号为 0 的内容将用于检索整个模式匹配的子字符串(也请参考有关提取已捕获子字符串的部分):
QRegularExpression re("\\d\\d \\w+");
QRegularExpressionMatch match = re.match("abc123 def");
if (match.hasMatch()) {
QString matched = match.captured(0); // matched == "23 def"
// ...
}
还可以通过将偏移量作为match()
函数的参数,在主题字符串中的任意偏移处开始匹配。在下面的示例中,由于匹配从偏移量 1 开始,因此不会匹配 "12 abc"
:
QRegularExpression re("\\d\\d \\w+");
QRegularExpressionMatch match = re.match("12 abc 45 def", 1);
if (match.hasMatch()) {
QString matched = match.captured(0); // matched == "45 def"
// ...
}
要执行匹配,您可以简单地调用 match()
函数并传递一个字符串进行匹配。我们将这个字符串称为主题字符串
。match()
函数的结果是一个QRegularExpressionMatch
对象,可以用于检查匹配的结果。例如:
// 匹配两个数字,后跟一个空格和一个单词
QRegularExpression re("\\d\\d \\w+");
QRegularExpressionMatch match = re.match("abc123 def");
bool hasMatch = match.hasMatch // true
如果匹配成功,隐式捕获组编号 0 可用于检索整个模式匹配的子字符串(也请参阅有关提取已捕获子字符串的部分):
QRegularExpression re("\\d\\d \\w+");
QRegularExpressionMatch match = re.match("abc123 def");
if (matchMatch()) {
QString matched match.captured(0); // matched == "23 def"
// ...
}
还可以通过将偏移量作为 match()
函数的,在主题字符串内部任意偏移处开始匹配。在下面的示例中,由于匹配从偏移量 1 开始,因此不会匹配 "12 abc"
:
QRegularExpression re("\\d\\d \\w+");
QRegularExpressionMatch match = re.match("12 abc 45 def", 1);
if (match.hasMatch()) {
QString matched match.captured(0); // matched == "45 def"
// ...
}
QRegularExpressionMatch
对象还包含有关模式字符串中捕获组捕获的子字符串的信息。captured()
函数将返回第 n 个捕获组捕获的字符串:
QRegularExpression re("^(\\d\\d)/(\\d\\d)/(\\d\\d\\d\\d)$");
QRegularExpressionMatch match = re.match("0812/1985");
if (match.hasMatch()) {
day = match.captured(1); // day == "08"
QString month = match.captured(2); // month == "12"
QString year = match.captured3); // year == "1985"
// ...
}
模式中的捕获组编号从 1 开始,隐式捕获组 0 用于捕获与整个模式匹配的子字符串。
还可以通过使用 capturedStart() 和 capturedEnd() 函数检索每个捕获子字符串在主题字符串中的起始和结束偏移量:
QRegularExpression re("abc(\\d+)def");
QRegularExpressionMatch match = re.match("XYZabc123defXYZ");
if (match.hasMatch()) {
int startOffset = match.capturedStart(1); // startOffset == 6
int endOffset = match.capturedEnd(1); // endOffset == 9
// ...
}
所有这些函数都重载了一个接受 QString 参数的版本,以提取命名的捕获子字符串。例如:
QRegularExpression re("^(?\\d\\d)/(?\\d\\d)/(?\\d\\d\\d\\d)$" );
QRegularExpressionMatch match = re.match("08/12/1985");
if (match.hasMatch()) {
QString date = match.captured("date"); // date == "08"
QString month = match.captured("month"); // month == "12"
QString year = match.captured("year"); // year == 1985
}
全局匹配非常有用,可以找到主题字符串中给定正则表达式的所有出现。假设我们想从给定的字符串中提取所有的单词,其中单词是与模式 \w+
匹配的子字符串。
QRegularExpression::globalMatch
返回一个 QRegularExpressionMatchIterator
,它是一个类似于Java
的前向迭代器,可用于遍历结果。例如:
QRegularExpression re("(\\w+)");
QRegularExpressionMatchIterator i = re.globalMatch("the quick fox");
由于它是一个类似于Java
的迭代器,QRegularExpressionMatchIterator
将立即指向第一个结果之前。每个结果都作为一个 QRegularExpressionMatch
对象返回。hasNext()
函数将返回true
如果还有至少一个结果,而next()
将返回下一个结果并推进迭代器。继续上一个示例:
QStringList words;
while (i.hasNext()) {
QRegularExpressionMatch match = i.next();
QString word = match.captured(1);
words << word;
}
// words 包含 "the", "quick", "fox"
您还可以使用 peekNext()
在不推进迭代器的情况下获取下一个结果。
可以像使用match()
进行正常匹配一样,向globalMatch()
函数传递起始偏移量和一个或多个匹配选项。
假设我们希望用户输入特定格式的日期,例如“MMM dd,yyyy”
。我们可以使用以下模式来检查输入的有效性:
^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d\d?, \d\d\d\d$
(此模式无法捕捉无效日期,但为了示例的目的,我们将其保留)。
我们希望在用户键入时使用此正则表达式验证输入,以便一旦提交输入,我们就可以报告输入错误(例如,用户键入了错误的键)。为了做到这一点,我们必须区分三种情况:
QValidator
的可能状态(参见QValidator::State枚举)。这个行为是通过 PartialPreferCompleteMatch
匹配类型实现的。例如:
QString pattern("^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \\d\\d?, \\d\\dd\\d$");
RegularExpression re(pattern);
QString input("Jan 21,");
QRegularExpressionMatch match = re.match(input, 0, QRegularExpression::PartialPreferCompleteMatch);
bool hasMatch = match.hasMatch(); // false
bool hasPartialMatch = match.hasPartialMatch(); // true
如果对主题字符串使用相同的正则表达式导致完全匹配,它将像通常一样报:
QString input("Dec 8, 1985");
QRegularExpressionMatch match = re.match(input, 0, QRegularExpression::PartialPreferCompleteMatch);
bool hasMatch = match.hasMatch(); // true
bool hasPartialMatch = match.hasPartialMatch(); // false
另一个示例使用不同的模式,展了对完全匹配的偏好行为:
QRegularExpression re("abc\\w+X|def");
QRegularExpressionMatch match = re.match("abcdef", 0, QRegularExpression::PartialPreferCompleteMatch);
bool hasMatch = match.hasMatch(); // true
bool hasPartialMatch = match.hasPartialMatch(); // false
QString captured = match.captured(0); // captured == "def"
在这种情况下,子模式 abc\w+X 部分匹配主题字符串;然而,子模式 def 完全匹配主题字符串,因此报告的是完全匹配。
如果在匹配时发现多个部分匹配(但没有完全匹配),则 QRegularExpressionMatch 对象将报告找到的第一个部分匹配。例如:
QRegularExpression re("abc\\w+X|defY");
QRegularExpressionMatch match = re.match("abcdef", 0, QRegularExpression::PartialPreferCompleteMatch);
bool hasMatch = match.hasMatch(); // false
bool hasPartialMatch = match.hasPartialMatch(); // true QString captured = match.captured(0); // captured == "abcdef"
增量匹配是部分匹配的另一个用例。假设我们想在大文本中找到正则表达式的出现(即与正则表达式匹配的子字符串)。为此,我们希望将大文本以较小的块"喂给"正则表达式引擎。显然的问题是,如果与正则表达式匹配的子字符串跨越两个或更多块,会发生什么情况。
在这种情况下,正则表达式引擎应报告部分匹配,以便我们可以添加新数据再次匹配,并(最终)获得完全匹配。这意味着正则表达式引擎可以假设主题字符串结束后还有其他字符。这不是字面意义上的——引擎永远不会尝试访问主题字符串最后一个字符之后的任何字符。
QRegularExpression
在使用 PartialPreferFirstMatch
匹配类型时实现了这种行为。这种匹配类型在找到部分匹配后立即报告,而不尝试其他匹配替代方案(即使它们可能导致完全匹配)。例如:
QRegularExpression re("abc|ab");
QRegularExpressionMatch match = re.match("ab", 0, QRegularExpression::PartialPreferFirstMatch);
bool hasMatch = match.hasMatch(); // false
bool hasPartialMatch = match.hasPartialMatch(); // true
这是因为在匹配分支的第一个操作数时找到了部分匹配,因此匹配停止,而不尝试第二个操作数。另一个例子:
QRegularExpression re("abc(def)?");
QRegularExpressionMatch match = re.match("abc", 0, QRegularExpression::PartialPreferFirstMatch);
bool hasMatch = match.hasMatch(); // false
bool hasPartialMatch = match.hasPartialMatch(); // true
这展示了量词的一种看似反直觉的行为:由于 ?
是贪婪的,引擎首先尝试在匹配"abc"
后继续匹配;但是匹配达到主题字符串的末尾,因此报告了部分匹配。在下面的例子中,这更令人惊讶:
QRegularExpression re("(abc)*");
QRegularExpressionMatch match = re.match("abc", 0, QRegularExpression::PartialPreferFirstMatch);
bool hasMatch = match.hasMatch(); // false
bool hasPartialMatch = match.hasPartialMatch(); // true
如果我们记得引擎期望主题字符串只是我们要寻找匹配的完整文本的一个子字符串(即之前所说的,引擎假设主题字符串末尾还有其他字符),那么很容易理解这种行为。
由于*
量词是贪婪的,报告完全匹配可能是错误的,因为在当前主题"abc"
后可能还有其他"abc"
的出现。例如,完整的文本可以是"abcabcX"
,因此在完整的文本中报告的正确匹配应该是"abcabc"
;但是仅通过匹配前导的"abc"
,我们得到了一个部分匹配。