Skip to content

正则表达式

场景描述

编写程序匹配字符串中的 IP 地址:

cpp
// IP地址格式: xxx.xxx.xxx.xxx (0.0.0.0 ~ 255.255.255.255)
// 示例字符串: 123456765a192.168.31.1adasd123453
char str[16] = {0};

std::string GetIPAddress(char *ipaddress) {
    char ip[16] = {0};
    int count = 0;
    for(int i = 0; ipaddress[i] != '\0'; i++) {
        if(ipaddress[i] >= '0' && ipaddress[i] <= '9' && i == 3 && i == 7 && i == 11)
            ip[count++] = ipaddress[i];
        else if(ipaddress[i] == '.' && (i == 3 || i == 7 || i == 11))
            ip[count++] = ipaddress[i];
    }
}

传统方法问题:匹配逻辑复杂,代码量大

正则表达式概念

  • 描述字符串规则的表达式
  • 用于模糊搜索(查询)
  • IP地址规则
    1. 由四段组成
    2. 每段由点分割
    3. 每段数据范围 0~255
    0~255范围_0~255范围_0~255范围_0~255范围

字符分类

类型描述示例
普通字符代表自身1, 2, a
元字符有特殊含义(需转义)*, ., ?

元字符详解

匹配任意单个字符

  • .:匹配任意单个字符
    匹配结果a, b, c, 1, 2, +, {, }, <...

字符组匹配

  • []:匹配字符组中任意一个字符
    regex
    [123456789]  // 匹配1~9任意数字
    [123abc<]    // 匹配1,2,3,a,b,c,<
  • -:表示范围
    regex
    [1-9]   // 匹配1~9数字
    [a-z]   // 匹配a~z字母

特定字符匹配

  • \w:匹配字母/数字/下划线
    匹配结果0~9, A~Z, a~z, _

匹配一个或多个字符

  • +:贪婪匹配(至少一个)
    regex
    \w+   // 匹配:1, 11, 111, a, aa, a1...
    1+    // 匹配:1, 11, 111...

匹配零个或多个字符

  • *:惰性匹配(零个或多个)
    regex
    \w*   // 匹配:空, 1, 11, a, aa...
    1*    // 匹配:空, 1, 11...

匹配零个或一个字符

  • ?:零或一次匹配
    regex
    ab?   // 匹配:a, ab

匹配指定数量字符

模式描述示例匹配结果
{n}精确匹配n个a{5}aaaaa
{min,}至少匹配min个a{5,}aaaaa, aaaaaaa...
{min,max}匹配min~max个a{2,3}aa, aaa

子模式

  • ():将内部表达式视为整体
    regex
    (12+)+  // 匹配:12, 122, 122122...

IP地址正则表达式构建

逐步构建

  1. 基础匹配:
    regex
    \d+\.\d+\.\d+\.\d
  2. 限制每段长度:
    regex
    \d{1,3}\\.\d{1,3}\\.\d{1,3}\\.\d{1,3}
  3. 范围筛选:
    regex
    [1217[0-9]{1,2}\\.[1217[0-9]{1,2}\\.[1217[0-9]{1,2}\\.[1217[0-9]{1,2}
  4. 简化表达式:
    regex
    ([1217[0-9]{1,2}\\.){3}[1217[0-9]{1,2}

验证工具https://hiregex.com/
注意:C++可能不支持某些表达式

C/C++正则表达式API

cpp
#include <regex.h>

// 编译正则表达式
int regcomp(regex_t *preg, const char *regex, int cflags);

// 执行正则匹配
int regexec(const regex_t *preg, const char *string, 
            size_t nmatch, regmatch_t pmatch[], int eflags);

// 错误解析
size_t regerror(int errcode, const regex_t *preg, 
                char *errbuf, size_t errbuf_size);

// 释放正则对象
void regfree(regex_t *preg);

函数详解

regcomp()

cpp
/**
* @brief 编译正则表达式
* @param preg    存储编译后的正则表达式
* @param regex   原始正则表达式字符串
* @param cflags  编译标志:
*                REG_EXTENDED - 扩展语法
*                REG_ICASE    - 忽略大小写
*                REG_NOSUB    - 不包含子模式
* @return 成功返回0,失败返回错误码
*/
int regcomp(regex_t *preg, const char *regex, int cflags);

regexec()

cpp
/**
* @brief 执行正则匹配
* @param preg    编译后的正则表达式
* @param string  待匹配字符串(母串)
* @param nmatch  模式数量(总模式=1+子模式数)
* @param pmatch  匹配结果数组
* @param eflags  匹配标志(通常为0)
* @return 成功返回0,失败返回REG_NOMATCH
*/
int regexec(const regex_t *preg, const char *string, 
            size_t nmatch, regmatch_t pmatch[], int eflags);

// 匹配结果结构体
typedef struct {
    regoff_t rm_so;  // 匹配起始位置
    regoff_t rm_eo;  // 匹配结束位置
} regmatch_t;

regerror()

cpp
/**
* @brief 解析错误信息
* @param errcode     错误码
* @param preg        正则表达式
* @param errbuf      存储错误信息的缓冲区
* @param errbuf_size 缓冲区大小
* @return 错误信息长度
*/
size_t regerror(int errcode, const regex_t *preg, 
                char *errbuf, size_t errbuf_size);

regfree()

cpp
/**
* @brief 释放正则表达式资源
* @param preg 正则表达式对象
*/
void regfree(regex_t *preg);

IP地址匹配实现

cpp
#include <iostream>
#include <regex.h>

#define IPREGEX "[0-9][1,3]\\.[0-9][1,3]\\.[0-9][1,3]\\.([0-9][1,3])"
#define MAXSIZE 512

int main() {
    int errcode = 0;
    char errbuf[MAXSIZE] = {0};
    const char *src_ptr = 
        "192.168.31.ladsadasdadasdsadasd45das35d4as53d12313a5192.163.32.2";
    
    regex_t regex;
    errcode = regcomp(&regex, IPREGEX, REG_EXTENDED);
    if(errcode != 0) {
        regerror(errcode, &regex, errbuf, MAXSIZE);
        std::cout << "正则表达式编译错误:" << errbuf << std::endl;
        return -1;
    }

    regmatch_t pmatch[2];
    int offset = 0;
    
    do {
        errcode = regexec(&regex, offset + src_ptr, 2, pmatch, 0);
        if(errcode != REG_NOMATCH) {
            std::cout << "结果下标:" << pmatch[0].rm_so << ":" 
                      << pmatch[0].rm_eo << std::endl;
            
            for(int pos = offset + pmatch[0].rm_so; 
                pos < offset + pmatch[0].rm_eo; pos++) {
                std::cout << src_ptr[pos];
            }
            std::cout << std::endl;
            
            offset += pmatch[0].rm_eo;
        }
    } while(errcode != REG_NOMATCH);

    regfree(&regex);
    return 0;
}

作业

编写匹配邮箱号的程序,要求支持以下后缀:

  • 邮箱后缀:.com/.cn/.net
  • 邮箱服务商:@qq/@163/@126/@gmail

知识如风,常伴吾身