正则表达式零宽断言

适用场景:查找/替换以 xxx 开头,或以 xxx 结尾,但不包括 xxx 的字符串。

零宽断言

用法

含义

​(?=exp)​​ 零宽度正预测先行断言

​exp1(?=exp2)​

​exp1​​ 之后必须匹配 ​​exp2​​,但匹配结果不含 ​​exp2​

​(?!exp)​​ 零宽度负预测先行断言

​exp1(?!exp2)​

​exp1​​ 之后必须不匹配 ​​exp2​

​(?<=exp)​​ 零宽度正回顾后发断言

​(?<=exp0)exp1​

​exp1​​ 之前必须匹配 ​​exp0​​,但匹配结果不含 ​​exp0​

​(?<!exp)​​ 零宽度负回顾后发断言

​(?<!exp0)exp1​

​exp1​​ 之前必须不匹配 ​​exp0​

示例:提取​​【123】​​中的 ​​123​​:​​(?<=【)\d+(?=】)​

问题描述

正则表达式匹配形似 ​​qq=123456​​ 的字符串,从中提取 ​​123456​​,但不包括 ​​qq=​​。首先想到的是直接利用零宽断言 lookbehind 去匹配,正则表达式很容易写出来 ​​(?<=qq=)[0-9]+​​,但是在 C++ 运行阶段报错:

terminate called after throwing an instance of 'std::regex_error'
what(): Invalid special open parenthesis.
Aborted (core dumped)


问题分析

目前 C++ 标准库正则表达式不支持零宽后行断言(也叫零宽度正回顾后发断言,lookbehind),即 ​​(?<=exp)​​ 和 ​​(?<!exp)​​ 语法。但支持零宽前行断言(lookahead)。

Finally, flavors like std::regex and Tcl do not support lookbehind at all, even though they do support lookahead. JavaScript was like that for the longest time since its inception. But now lookbehind is part of the ECMAScript 2018 specification. As of this writing (late 2019), Google’s Chrome browser is the only popular JavaScript implementation that supports lookbehind. So if cross-browser compatibility matters, you can’t use lookbehind in JavaScript.

解决方案
  1. 构造 regex 时指定可选标志,使用其他正则表达式语法 ==> 验证失败 ????
  2. 把待提取部分用()括起来,作为一个独立子表达式 ==> 验证可行 ????

示例代码

#include <iostream>
#include <regex>
#include <string>

using namespace std;
using namespace regex_constants;

int main()
{
string seq = "[optional]qq=123456;";
// string pattern = "(?<=qq=)[0-9]+"; // C++ 正则表达式不支持 lookbehind,运行时报错
string pattern = "qq=([0-9]+)"; // 将数字部分单独作为一个子表达式
regex r(pattern /*, extended*/); // 可以在这里修改默认正则表达式语法,然而并没有什么用
smatch results;

if (regex_search(seq, results, r)) {
cout << "regex_search --> true; results.size() --> " << results.size() << endl;
cout << results[0] << endl; // 打印整个匹配
cout << results[1] << endl; // 打印第一个正则子表达式
cout << results[2] << endl;
} else {
cout << "regex_search --> false" << endl;
}

if (regex_match(seq, results, r)) {
cout << "regex_match --> true; results.size() --> " << results.size() << endl;
cout << results[0] << endl; // 打印整个匹配
cout << results[1] << endl; // 打印第一个正则子表达式
cout << results[2] << endl;
} else {
cout << "regex_match --> false" << endl; // seq 中的“[optional]”和“;”与 pattern 不匹配
}
}


输出结果

$ g++ regex.cpp && ./a.out
regex_search --> true; results.size() --> 2
qq=123456
123456

regex_match --> false


补充

​regex_match(seq[, match], exp[, matchFlag])​​ 整个 seq 与 exp 匹配时返回 true。

​regex_search(seq[, match], exp[, matchFlag])​​ 找到一个匹配字串就停止查找,返回 true。

​match.size()​​ 匹配失败返回 0,否则返回子表达式数量。

​match.str(n)​​ 与第 n 个子表达式匹配的 ​​string​​。n 默认为 0,且小于 ​​match.size()​

​match[n]​​ 对应第 n 个子表达式的 ​​ssub_match​​ 对象。n 默认为 0,且小于 ​​match.size()​

C++ regex 默认采用 ECMAScript 语法。功能强大,使用广泛,建议使用默认 ECMAScript。

C++ 正则表达式的语法在运行时解析、编译,非常慢,避免创建不必要的正则表达式。特别是在循环中。

可以通过 ​​R"(...)"​​ 来忽略 C++ 中的转义字符 ​​\​​,如 ​​regex r(R"(\d+)");​