当前位置:首页 > 公众号精选 > CPP开发者
[导读]↓推荐关注↓正则表达式可以说是软件开发中最常用的功能之一。本文将以C语言为例,介绍其中的正则表达式相关知识。前言当你想要判断许多字符串是否符合某个特定格式;当你想在一大段文本中查找出所有的日期和时间;当你想要修改大量日志中所有的时间格式,在这些情况下,正则表达式都能帮上忙。简单来...

推荐关注↓

正则表达式可以说是软件开发中最常用的功能之一。本文将以C 语言为例,介绍其中的正则表达式相关知识。

前言

当你想要判断许多字符串是否符合某个特定格式;当你想在一大段文本中查找出所有的日期和时间;当你想要修改大量日志中所有的时间格式,在这些情况下,正则表达式都能帮上忙。

简单来说,正则表达式描述了一系列规则,通过这些规则,可以在字符串中找到相关的内容,规则使得搜索的能力更加强大。匹配的过程由正则表达式引擎完成。开发者通常不需要关心正则表达式引擎的实现细节,直接使用其提供的能力即可。

正则表达式非常的常用,但真正精通它的人却不多。本文试图给大家讲解一些对于C 语言使用正则表达式的基础知识。

代码示例

本文中所贴出的代码示例可以到我的Github上获取:paulQuei/cpp-regex[1]

或者,你也可以直接通过下面这条命令获取所有源码:

git clone https://github.com/paulQuei/cpp-regex.git

C 中正则表达式的API基本上都位于头文件中。

为了简化书写,本文中给出的代码都已经默认做了以下操作:

#include 
#include 

using namespace std;

入门示例

为了使大家有一个直观的感受,文章的开头先通过一些入门示例给大家一个直观的感受。在这个基础之上,再详细讲解其中的细节。

使用正则表达式的大致流程如下:首先你有一段需要处理的文本。这可能是一个字符串对象,也可能是一个文本文件,或者是一大堆日志。接下来你会有特定的目标,例如:找出文本中所有的时间和日期。这个时候你就需要根据可能的格式写出具体的正则表达式,例如,日期的格式是:2020-01-01,那么你的正则表达式可能是这样:\d{4}-\d{2}-\d{2}。(你现在不必纠结与这个正则表达式是什么意思,因为这是本文接下来要讲解的内容。)

有了正则表达式之后,你需要将你的文本和正则表达式交给正则表达式引擎 – 由C 语言(或者其他语言)提供。引擎会在文本中搜索到匹配的结果。这个结果的格式可能是包含了多个组,例如:你可能需要分离出年份和月份。有了引擎返回的结果之后,你就可以进一步处理了。

img
使用正则表达式的流程大体都是一致的,下面是最常见的三种使用方式。

匹配

匹配是判断给定的字符串是否符合某个正则表达式。例如:你想判断当前文本是否全部由数字构成。

下面是一段代码示例:

string s1 = "ab123cdef"; // ①
string s2 = "123456789"; // ②

regex ex("\\d "); // ③

cout << s1 << " is all digit: " << regex_match(s1, ex) << endl; // ④
cout << s2 << " is all digit: " << regex_match(s2, ex) << endl; // ⑤
在这段代码中:

  1. 这是一个包含了数字和字母的字符串
  2. 这是一个只包含了数字的字符串
  3. 这是我们的正则表达式,它表示:有多个数字
  4. 通过regex_match判断第一个字符串是否匹配,这里将返回false
  5. 通过regex_match判断第二个字符串是否匹配,这里将返回true
这段代码输出如下:

ab123cdef is all digit: 0
123456789 is all digit: 1
请注意,正则表达式有它自身的语法。这与C 的语法是两回事。C 编译器只会检查C 代码的语法。因此,即便你的代码通过了C 编译器的语法检查,但在运行的时候,由于正则表达式的语义,还可能出现正则表达式的错误。正则表达式的错看起来类似这样:terminating with uncaught exception of type std::__1::regex_error: The expression contained an invalid escaped character, or a trailing escape.

搜索

还有一些时候,我们要判断的并非是文本的全体是否匹配。而是在一大段文本中搜索匹配的目标。

下面是一段代码示例,这段示例演示了在一个字符串中查找数字:

string s = "ab123cdef"; // ①
regex ex("\\d ");    // ②

smatch match; // ③
regex_search(s, match, ex); // ④

cout << s << " contains digit: " << match[0] << endl; // ⑤
  1. 这是一个包含了数字和字母的字符串
  2. 和前面一样的正则表达式
  3. 通过std::smatch来保存匹配的结果。除了std::smatch,还有std::cmatch也很常用。前者是以std::string的形式返回结果,后者是以const char*的形式返回结果。
  4. 通过regex_search函数搜索结果
  5. 打印出匹配的结果
这段代码输出如下:

ab123cdef contains digit: 123

替换

最后,使用正则表达式的还有一个常见功能是文本替换。很多的编辑器都有这样的功能。

例如,下图是我的Sublime编译器,在搜索替换文本的时候,可以使用正则表达式,这时搜索的能力就更加强大了。“Find:”部分可以通过正则表达式来描述待替换的字符串,“Replace:”部分填写替换的字符串。

img
下面是在C 中使用正则表达式完成字符串替换的代码示例:

string s = "ab123cdef"; // ①
regex ex("\\d ");    // ②

string r = regex_replace(s, ex, "xxx"); // ③

cout << r << endl; // ④
  1. 仍然是前面这个字符串
  2. 仍然是同样的正则表达式
  3. 通过regex_replace完成替换
  4. 通过cout输出结果
最终输出的字符串如下:

abxxxcdef
通过上面的三个示例我们看到,regex_matchregex_searchregex_replace三个函数是正则表达式的核心,它们会运行正则表达式引擎完成匹配,查找和替换任务。

正则表达式文法

文法

C 中内置了多种正则表达式文法,在创建正则表达式的时候可以通过参数来选择。

它们如下表所示:

文法说明
ECMAScriptECMAScript正则表达式语法[2],默认选项
basic基础POSIX正则表达式语法[3]
extended扩展POSIX正则表达式语法[4]
awkawk工具的正则表达式语法[5]
grepgrep工具的正则表达式语法[6]
egrepgrep工具的正则表达式语法[7]
不同的文法在表达上有一些不同,如果你原先已经很熟悉awk或者egrep文法的正则表达式,你可以直接使用它们。对于其他人来说,我们直接使用默认的ECMAScript文法即可(Javascript的正则表达式也是使用ECMAScript文法)。

grep的全称是Global Regular Expression Print。这个名字是在提示我们,它本身与正则表达式的历史有着特定的联系。

C 中的 ECMAScript 正则表达式文法是 ECMA-262 文法[8],你可以点击链接查看详细内容。

下文中,我们仅会讲解该标准下的正则表达式。

Raw string literal

在代码中写字符串有时候是比较麻烦的,因为很多字符需要通过反斜杠转义。当有多个反斜杠连在一起时,就很容易写错或者理解错了。

当通过字符串来写正则表达式时,这个问题就更严重了。因为正则表达式本身也有一些字符需要转义。例如,对于这样一个字符串 "('(?:[^\\\\']|\\\\.)*'|\"(?:[^\\\\\"]|\\\\.)*\")|" 大部分人恐怕很难一眼看出其含义了。

在正则表达式很复杂的时候,推荐大家使用Raw string literal来表达。这种表达式是告诉编译器:这里的内容是纯字符串,因此不再需要增加反斜杠来转义特殊字符。

Raw string literal的格式如下:

R"delimiter(raw_characters)delimiter"
这其中:

  • delimiter是可选的分隔符,通常不用写
  • raw_characters是具体的字符串
也就是说,R"(content)"中的content是你需要的字符串本身。

下面是一个代码示例:

string s = R"("\w\\w\\\w)";cout << s << endl;
它将输出:

"\w\\w\\\w
可以看到,这里的双引号和反斜杠不会被解释成转义字符,而是当成字符串内容本身,因此会原样输出。这样就减少了转义字符的复杂度,于是更容易理解了。

特殊字符

正则表达式本身定义了一些特殊的字符,这些字符有着特殊的含义。它们如下表所示。

字符说明
.匹配任意字符
[字符类的开始
]字符类的结束
{量词重复数开始
}量词重复数结束
(分组开始
)分组结束
\转义字符
\转义字符自身
*量词,0个或者多个
量词,1个或者多个
?量词,0个或者1个
|
^行开始;否定
$行结束
\n换行
\tTab符
\xhhhh表示两位十六进展表示的Unicode字符
\xhhhhhhhh表示四位十六进制表示的Unicode字符串
这些字符并不少,刚开始接触可能记不住,但随着下文的讲解,相信你会逐渐熟悉它们。

字符类

字符类,顾名思义:是对字符的分类。

例如:1234567890这些都属于数字字符类。除此之外,还有其他的分类,它们如下表所示:

字符类简写说明
[[:alnum:]]
字母和数字
[_[:alnum:]]\w字母,数字以及下划线
[^_[:alnum:]]\W非字母,数字以及下划线
[[:digit:]]\d数字
[^[:digit:]]\D非数字
[[:space:]]\s空白字符
[^[:space:]]\S非空白字符
[[:lower:]]
小写字母
[[:upper:]]
大写字母
[[:alpha:]]
任意字母
[[:blank:]]
非换行符的空白字符
[[:cntrl:]]
控制字符
[[:graph:]]
图形字符
[[:print:]]
可打印字符
[[:punct:]]
标点字符
[[:xdigit:]]
十六进制的数字字符
这里我们可以看到:

  • 字符类通过[]作为标识,因此这两个字符是正则表达式的中的特殊字符。如果是想使用这两个字符本身,需要对它们进行转义。
  • []内部,通过[:xxx:]来描述字符类的名称。
  • []中可以通过^表示否定,即:字符类的反面。
  • 字母,数字和空白字符由于这些字符类非常常用,因此它们有简写的方法。简写使得正则表达式更加简洁,但表达的含义是一样的。
接下来我们看一个代码示例:

#include 
#include 

using namespace std;

static void search_string(const string
本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
关闭
关闭