在Visual Studio 2010环境下,如果C语言想要使用<regex.h>头文件进行正则表达式匹配,则需要pcre3.dll这个动态链接库,可以去网上下载。
下载的网址是:
Pcre for Windowspcre {whatisit}https://gnuwin32.sourceforge.net/packages/pcre.htm下载的栏目是:Binaries和Developer files。
下载下来后是pcre-7.0-bin.zip和pcre-7.0-lib.zip两个压缩包。
第一节 建立Visual Studio 2010工程
建立一个VS2010控制台空白项目:
新建一个main.c源文件:
复制以下代码内容:
// 函数参考文档: https://www.man7.org/linux/man-pages/man3/regcomp.3.html
#include <regex.h>
#include <stdio.h>
#pragma comment(lib, "pcre.lib")
int main()
{
int ret;
regex_t reg;
ret = regcomp(®, "\\w{8}", 0); // 设置正则表达式
if (ret == 0)
{
ret = regexec(®, "1234efGH", 0, NULL, 0); // 匹配第一个字符串
if (ret == 0)
printf("match\n");
else if (ret == REG_NOMATCH)
printf("mismatch\n");
ret = regexec(®, "1234efG", 0, NULL, 0); // 匹配第二个字符串
if (ret == 0)
printf("match\n");
else if (ret == REG_NOMATCH)
printf("mismatch\n");
regfree(®);
}
return 0;
}
在项目中建立pcre-7.0文件夹,并将下面几个h文件和lib文件解压到其中。
pcre-7.0-lib.zip\include\pcre.h
pcre-7.0-lib.zip\include\pcreposix.h
pcre-7.0-lib.zip\include\regex.h
pcre-7.0-lib.zip\lib\pcre.lib
在VS工程属性中附加pcre-7.0包含目录和库目录:
这时已经可以成功编译C程序,但是程序因缺少dll文件无法运行。
将pcre-7.0-bin.zip\bin\pcre3.dll解压到exe文件所在的Debug目录下(如C:\Documents and Settings\Octopus\桌面\pcre\regtest\Debug)就可以成功运行了。
第二节 判断正则表达式是否匹配某个字符串
C语言使用正则表达式函数前,需要先包含regex.h头文件。
#include <regex.h>
regex_t表示一个正则表达式对象,使用regcomp函数可根据正则表达式字符串创建正则表达式对象。
regcomp函数的原型如下:
int regcomp(regex_t *preg, const char *regex, int cflags);
preg参数用于保存待创建的正则表达式对象,是一个输出参数。
regex参数是正则表达式字符串,请注意C语言字符串中所有用于正则表达式的反斜杠都要双写。
cflags参数可指定下列一个或多个选项(用|连接多个选项)。
REG_EXTENDED:使用扩展正则表达式语法(POSIX Extended Regular Expression syntax)。
REG_ICASE:匹配时忽略大小写。(比如"^\\w+gh$"可匹配"1234efGH")
REG_NOSUB:后面regexec函数只判断字符串是否匹配,不返回匹配了哪些部分,也就是pmatch和nmatch参数将被忽略。
REG_NEWLINE:允许^和$符号匹配\n换行符。(原本正则表达式"^\\w+gh$"不能匹配"aaa\nagh\nccc",加上REG_NEWLINE选项后,就可以成功匹配第二行的agh)
正则表达式对象创建成功时返回0,返回其他值表示创建失败。
正则表达式对象使用完毕后一定要用regfree函数释放掉。
void regfree(regex_t *preg);
使用regexec函数判断正则表达式是否匹配指定的字符串。
int regexec(const regex_t *preg, const char *string, size_t nmatch, regmatch_t *pmatch, int eflags);
preg参数是待使用的正则表达式。
string是要判断的字符串。
nmatch和pmatch参数下一节再讲,本节先分别设置成0和NULL。(在C语言中NULL特指空指针,不能用来给非指针变量赋值)
eflags参数是指定一些选项,这个本文也不讲,笔者认为没什么用,请自行参考官方文档。
字符串匹配成功时返回0,失败时返回REG_NOMATCH。
示例代码:
#include <regex.h>
#include <stdio.h>
#pragma comment(lib, "pcre.lib")
int main()
{
int ret;
regex_t reg;
ret = regcomp(®, "^\\w+gh$", REG_ICASE | REG_NOSUB); // 设置正则表达式
if (ret == 0)
{
ret = regexec(®, "1234efGH", 0, NULL, 0); // 匹配第一个字符串
if (ret == 0)
printf("match1\n");
else if (ret == REG_NOMATCH)
printf("mismatch1\n");
ret = regexec(®, "1234efgh", 0, NULL, 0); // 匹配第二个字符串
if (ret == 0)
printf("match2\n");
else if (ret == REG_NOMATCH)
printf("mismatch2\n");
regfree(®);
}
ret = regcomp(®, "^\\w+gh$", REG_NEWLINE | REG_NOSUB);
if (ret == 0)
{
ret = regexec(®, "aaa\nagh\nccc", 0, NULL, 0);
if (ret == 0)
printf("match3\n");
else if (ret == REG_NOMATCH)
printf("mismatch3\n");
regfree(®);
}
ret = regcomp(®, "^\\w+gh$", REG_NOSUB);
if (ret == 0)
{
ret = regexec(®, "aaa\nagh\nccc", 0, NULL, 0);
if (ret == 0)
printf("match4\n");
else if (ret == REG_NOMATCH)
printf("mismatch4\n");
regfree(®);
}
return 0;
}
程序运行结果:
第三节 判断正则表达式匹配了某个字符串的哪些部分
在实际应用中,除了判断正则表达式是否匹配某个字符串外,有时我们还想知道到底匹配的是字符串中的哪部分,还有正则表达式里面的小括号都匹配了哪些部分。
这时,regexec函数的nmatch和pmatch参数就派上用场了。请注意要使用这两个参数的话,regcomp函数不能指定REG_NOSUB选项。
首先我们要声明一个regmatch_t数组,数组长度应该是正则表达式里面小括号的个数加上1。
比如"(\\w{2})(\\w+)"这个正则表达式有两个小括号,那么数组的长度就应该为2+1=3。
regmatch_t matches[3];
或者用malloc动态分配:
regmatch_t *matches = malloc(3 * sizeof(regmatch_t));
regcomp函数返回的regex_t结构体里面的re_nsub成员的值就是小括号的个数,所以上面这句话还可以写成:
regmatch_t *matches = malloc((reg.re_nsub + 1) * sizeof(regmatch_t));
然后,regexec函数的nmatch参数填数组长度(3),pmatch参数填数组名(matches)。
matches[0]用来存放整个正则表达式的匹配结果,matches[1~2]用来存放正则表达式里面的两个小括号的匹配结果。
matches[x].rm_so表示匹配的字符串的起始下标,matches[x].rm_eo表示匹配的字符串的结束下标。
假如要表示"abcdef"里面的"cde",那么rm_so=2,rm_eo=5,两者之差rm_eo-rm_so=5-2=3就是"cde"字符串的长度。
我们来看例程:
#include <regex.h>
#include <stdio.h>
#include <string.h>
#pragma comment(lib, "pcre.lib")
// 提取匹配的子字符串
const char *get_matched_str(const char *str, const regmatch_t *match)
{
static char buffer[1024];
int len;
len = match->rm_eo - match->rm_so; // 子字符串的长度 = 结束下标 - 开始下标
if (len > sizeof(buffer) - 1)
len = sizeof(buffer) - 1; // 保证不超过buffer缓冲区的大小
memcpy(buffer, str + match->rm_so, len); // 复制子字符串的内容到buffer缓冲区
buffer[len] = '\0';
return buffer;
}
// 打印正则表达式本身匹配的子字符串, 和所有小括号匹配的部分
void print_matches(const regex_t *reg, const char *str, const regmatch_t *matches)
{
int i;
printf("正则表达式本身匹配的子字符串: %s\n", get_matched_str(str, &matches[0]));
printf("小括号的个数: %d\n", reg->re_nsub);
for (i = 1; i <= (int)reg->re_nsub; i++)
printf("第%d个小括号匹配的子字符串: %s\n", i, get_matched_str(str, &matches[i]));
}
// 单次匹配
int match_once(const regex_t *reg, const char *str)
{
int found = 0;
int ret;
regmatch_t *matches;
matches = malloc((reg->re_nsub + 1) * sizeof(regmatch_t));
if (matches != NULL)
{
ret = regexec(reg, str, reg->re_nsub + 1, matches, 0);
if (ret == 0)
{
printf("匹配成功。\n");
found = 1;
print_matches(reg, str, matches);
}
else if (ret == REG_NOMATCH)
printf("匹配失败。\n");
free(matches);
}
return found;
}
int main()
{
int ret;
regex_t reg;
ret = regcomp(®, "(\\w{2})(\\w+)", 0); // 设置正则表达式
if (ret == 0)
{
match_once(®, "https://zhidao.baidu.com/");
regfree(®);
}
return 0;
}
程序运行结果为:
可以看到正则表达式匹配了"https://zhidao.baidu.com/"字符串里面的"https",两个小括号分别匹配了ht和tps。
实际上这个字符串还有其它匹配的部分,比如zhidao、baidu和com。
我们可以写一个多次匹配的函数,把一个字符串中所有匹配的地方都找出来。
// 多次匹配
int match_all(const regex_t *reg, const char *str)
{
int count = 0;
int offset = 0;
regmatch_t *matches;
matches = malloc((reg->re_nsub + 1) * sizeof(regmatch_t));
if (matches != NULL)
{
while (regexec(reg, str + offset, reg->re_nsub + 1, matches, 0) == 0)
{
count++;
printf("[第%d次匹配] %s\n", count, str + offset);
print_matches(reg, str + offset, matches);
offset += matches[0].rm_eo;
}
free(matches);
}
printf("总共匹配了%d次\n", count);
return count;
}
第四节 将匹配部分替换成另外一个字符串
刚才的多次匹配的函数还可以改写成正则表达式字符串替换函数,把所有匹配的地方都替换成指定字符串。
当然,为了简单起见,没有做识别$1、$2这些符号的功能。
#include <regex.h>
#include <stdio.h>
#include <string.h>
#pragma comment(lib, "pcre.lib")
// 多次替换
char *replace_all(const regex_t *reg, const char *str, const char *replacement)
{
char *new_str;
int i, len = 0;
int rlen;
int offset;
regmatch_t match;
// 先计算替换后的字符串有多长
rlen = strlen(replacement);
offset = 0;
while (regexec(reg, str + offset, 1, &match, 0) == 0)
{
len += match.rm_so + rlen; // 没匹配上的长度 + 替换的子字符串的长度
offset += match.rm_eo; // 继续下一次匹配
}
len += strlen(str + offset); // 剩余没匹配上的长度
// 生成替换后的字符串
new_str = malloc(len + 1);
if (new_str == NULL)
return NULL;
i = 0;
offset = 0;
while (regexec(reg, str + offset, 1, &match, 0) == 0)
{
if (match.rm_so > 0)
{
memcpy(new_str + i, str + offset, match.rm_so);
i += match.rm_so;
}
if (rlen > 0)
{
memcpy(new_str + i, replacement, rlen);
i += rlen;
}
offset += match.rm_eo;
}
if (i < len)
memcpy(new_str + i, str + offset, len - i);
new_str[len] = '\0';
return new_str;
}
int main()
{
char *str;
int ret;
regex_t reg;
ret = regcomp(®, "(\\w{2})(\\w+)", 0); // 设置正则表达式
if (ret == 0)
{
str = replace_all(®, "https://zhidao.baidu.com", "xy");
if (str != NULL)
{
printf("替换后的字符串: %s\n", str);
free(str);
}
regfree(®);
}
return 0;
}
第五节 替换字符串时反向引用匹配的部分
在实际应用中,有时我们还想在替换字符串时反向引用正则表达式匹配的子字符串。
例如,用$0引用整个正则表达式匹配的内容,用$1引用第一个小括号匹配的内容,用$2引用第二个小括号匹配的内容,一直到$9。$$则表示$符号本身。
这个功能用C语言实现起来其实并不难,稍微改下上一节的程序就可以了。
请看代码:
#include <regex.h>
#include <stdio.h>
#include <string.h>
#pragma comment(lib, "pcre.lib")
// 计算某一次替换的长度
// 支持$0~$9引用
// $$替换为$
int get_replacement_length(const char *replacement, const regmatch_t *matches, int nsub)
{
int i, len = 0;
while (*replacement != '\0')
{
if (*replacement == '$' && *(replacement + 1) != '\0')
{
if (*(replacement + 1) == '$')
{
len++;
replacement += 2;
continue;
}
i = *(replacement + 1) - '0';
if (i >= 0 && i <= 9 && i <= nsub)
{
len += matches[i].rm_eo - matches[i].rm_so;
replacement += 2;
continue;
}
}
len++;
replacement++;
}
return len;
}
// 生成某次替换后的字符串
char *get_replacement(const char *replacement, const char *src, const regmatch_t *matches, int nsub, int *rlen_out)
{
char *s;
int i, len = 0;
int rlen;
rlen = get_replacement_length(replacement, matches, nsub);
if (rlen_out != NULL)
*rlen_out = rlen;
s = malloc(rlen + 1);
if (s == NULL)
return NULL;
while (*replacement != '\0')
{
if (*replacement == '$' && *(replacement + 1) != '\0')
{
if (*(replacement + 1) == '$')
{
s[len] = '$';
len++;
replacement += 2;
continue;
}
i = *(replacement + 1) - '0';
if (i >= 0 && i <= 9 && i <= nsub)
{
memcpy(s + len, src + matches[i].rm_so, matches[i].rm_eo - matches[i].rm_so);
len += matches[i].rm_eo - matches[i].rm_so;
replacement += 2;
continue;
}
}
s[len] = *replacement;
len++;
replacement++;
}
s[len] = '\0';
return s;
}
// 多次替换
char *replace_all(const regex_t *reg, const char *str, const char *replacement)
{
char *new_str;
char *new_replacement;
int i, len = 0;
int rlen;
int offset;
regmatch_t matches[10];
// 先计算替换后的字符串有多长
offset = 0;
while (regexec(reg, str + offset, 10, matches, 0) == 0)
{
rlen = get_replacement_length(replacement, matches, reg->re_nsub);
len += matches[0].rm_so + rlen; // 没匹配上的长度 + 替换的子字符串的长度
offset += matches[0].rm_eo; // 继续下一次匹配
}
len += strlen(str + offset); // 剩余没匹配上的长度
// 生成替换后的字符串
new_str = malloc(len + 1);
if (new_str == NULL)
return NULL;
i = 0;
offset = 0;
while (regexec(reg, str + offset, 10, matches, 0) == 0)
{
if (matches[0].rm_so > 0)
{
memcpy(new_str + i, str + offset, matches[0].rm_so);
i += matches[0].rm_so;
}
new_replacement = get_replacement(replacement, str + offset, matches, reg->re_nsub, &rlen);
if (new_replacement != NULL)
{
if (rlen > 0)
{
memcpy(new_str + i, new_replacement, rlen);
i += rlen;
}
free(new_replacement);
}
offset += matches[0].rm_eo;
}
if (i < len)
memcpy(new_str + i, str + offset, len - i);
new_str[len] = '\0';
return new_str;
}
int main()
{
char *str;
int ret;
regex_t reg;
ret = regcomp(®, "(\\w{2})(\\w+)", 0); // 设置正则表达式
if (ret == 0)
{
str = replace_all(®, "https://zhidao.baidu.com", "($0,$1,$2,$3,$$)");
if (str != NULL)
{
printf("替换后的字符串: %s\n", str);
free(str);
}
regfree(®);
}
return 0;
}