首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C 語言中高效使用正則表達式:POSIX Regex API 完整指南與實戰案例

     編輯:關於C語言

前言

正則表達式是處理複雜字符串匹配、提取、替換的利器,廣泛應用於 Linux 工具(sed、awk、grep、vi)及腳本語言(Perl、Python、Bash)。C 語言雖無內建支持,但通過 POSIX 標準的 regex.h API(如 Philip Hazel 的 PCRE 庫、系統自帶 libregex),可在 C 程序中獲得強大的正則處理能力。本文系統講解編譯、匹配、釋放、錯誤處理全流程,並給出完整實戰代碼。

一、POSIX Regex 核心 API 流程


// 1. 編譯正則表達式 → regex_t 結構
int regcomp(regex_t *preg, const char *regex, int cflags);

// 2. 執行匹配 → regmatch_t 數組
int regexec(const regex_t *preg, const char *string, 
            size_t nmatch, regmatch_t pmatch[], int eflags);

// 3. 釋放資源
void regfree(regex_t *preg);

// 4. 錯誤信息獲取
size_t regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size);

二、關鍵數據結構


typedef struct {
    regoff_t rm_so; // 匹配起始偏移
    regoff_t rm_eo; // 匹配結束偏移
} regmatch_t;

// 整個正則匹配:pmatch[0]
// 第 1 個捕獲組:pmatch[1]
// 第 n 個捕獲組:pmatch[n]

三、詳細步驟解析

3.1 編譯正則表達式


regex_t reg;
int cflags = REG_EXTENDED; // 使用擴展正則語法(ERE)
// 可選標誌:REG_ICASE(忽略大小寫)、REG_NOSUB(不捕獲子表達式)、REG_NEWLINE(識別換行)

int ret = regcomp(®, pattern, cflags);
if (ret != 0) {
    char errbuf[256];
    regerror(ret, ®, errbuf, sizeof(errbuf));
    fprintf(stderr, "Regex compile failed: %s\n", errbuf);
    return -1;
}

3.2 執行匹配


#define MAX_MATCH 10
regmatch_t pm[MAX_MATCH];

ret = regexec(®, input_string, MAX_MATCH, pm, 0);
if (ret == REG_NOMATCH) {
    // 無匹配
} else if (ret != 0) {
    // 匹配過程出錯
    char errbuf[256];
    regerror(ret, ®, errbuf, sizeof(errbuf));
    fprintf(stderr, "Regex match error: %s\n", errbuf);
} else {
    // 成功匹配
    for (int i = 0; i < MAX_MATCH && pm[i].rm_so != -1; i++) {
        // pm[i].rm_so 到 pm[i].rm_eo 為第 i 個匹配子串
        int start = pm[i].rm_so;
        int end = pm[i].rm_eo;
        int len = end - start;
        printf("Match %d: [%.*s]\n", i, len, input_string + start);
    }
}

3.3 釋放資源(防內存洩漏)


regfree(®); // 必須調用,釋放 regcomp 分配的內部緩衝區
// 建議:每次 regcomp 後配對一次 regfree,即使立即重新編譯

3.4 錯誤處理

regerror 將錯誤碼轉為可讀字符串,errbuf_size 限制輸出長度,返回實際信息長度。

四、完整實戰案例:命令行正則匹配工具


#include 
#include 
#include 
#include 
#include 

// 安全子串提取
static char* substr(const char* str, unsigned start, unsigned end) {
    unsigned n = end - start;
    static char stbuf[256];
    if (n >= sizeof(stbuf)) n = sizeof(stbuf) - 1;
    strncpy(stbuf, str + start, n);
    stbuf[n] = '\0';
    return stbuf;
}

int main(int argc, char** argv) {
    if (argc < 2) {
        fprintf(stderr, "Usage: %s \n", argv[0]);
        return 1;
    }

    char *pattern = argv[1];
    regex_t reg;
    regmatch_t pm[10];
    const size_t nmatch = 10;
    char ebuf[128], lbuf[256];
    int lno = 0, ret;

    // 編譯正則
    ret = regcomp(®, pattern, REG_EXTENDED);
    if (ret != 0) {
        regerror(ret, ®, ebuf, sizeof(ebuf));
        fprintf(stderr, "Compile error: %s (pattern: %s)\n", ebuf, pattern);
        return 1;
    }

    // 逐行處理標準輸入
    while (fgets(lbuf, sizeof(lbuf), stdin)) {
        lno++;
        // 移除換行符
        int len = strlen(lbuf);
        if (len > 0 && lbuf[len-1] == '\n') lbuf[--len] = '\0';

        // 執行匹配
        ret = regexec(®, lbuf, nmatch, pm, 0);
        if (ret == REG_NOMATCH) continue; // 無匹配跳過
        if (ret != 0) {
            regerror(ret, ®, ebuf, sizeof(ebuf));
            fprintf(stderr, "Match error: %s (line: %s)\n", ebuf, lbuf);
            regfree(®);
            return 2;
        }

        // 輸出匹配結果
        for (int i = 0; i < nmatch && pm[i].rm_so != -1; i++) {
            if (i == 0) printf("%04d: %s\n", lno, lbuf); // 整行
            printf(" $%d=%s\n", i, substr(lbuf, pm[i].rm_so, pm[i].rm_eo));
        }
    }

    regfree(®);
    return 0;
}

編譯與運行


# 編譯
gcc regexp.c -o regexp -lregex

# 測試:匹配以 regex 開頭的單詞
./regexp 'regex[a-z]*' < regexp.c
# 輸出示例:
# 0003: #include 
#  $0=regex
# 0027: regex_t reg;
#  $0=regex
# 0054: z = regexec(?, lbuf, nmatch, pm, 0);
#  $0=regexec

五、進階用法與技巧

5.1 常用編譯標誌

標誌作用
REG_EXTENDED使用擴展正則語法(ERE),功能更強
REG_ICASE忽略大小寫匹配
REG_NOSUB不計算子表達式匹配位置,加速匹配
REG_NEWLINE^/$ 匹配行首尾,. 不匹配換行

5.2 常用匹配標誌

標誌作用
REG_NOTBOL字符串非行首,^ 不匹配
REG_NOTEOL字符串非行尾,$ 不匹配

5.3 捕獲組應用:提取關鍵字段


// 模式:(\\d{4})-(\\d{2})-(\\d{2}) 提取年月日
const char *pattern = "(\\d{4})-(\\d{2})-(\\d{2})";
// 匹配後:
// pm[0] = 整個匹配 "2024-01-15"
// pm[1] = 年 "2024"
// pm[2] = 月 "01"
// pm[3] = 日 "15"

5.4 多次匹配同一字符串(迭代匹配)


const char *str = "ab12cd34ef56";
const char *pattern = "([a-z]+)(\\d+)";
regex_t reg;
regcomp(®, pattern, REG_EXTENDED);

regmatch_t pm[3];
int offset = 0;
while (1) {
    int ret = regexec(®, str + offset, 3, pm, 0);
    if (ret == REG_NOMATCH) break;
    if (ret != 0) { /* error */ break; }
    
    printf("Full: %.*s, Letters: %.*s, Digits: %.*s\n",
        pm[0].rm_eo - pm[0].rm_so, str + offset + pm[0].rm_so,
        pm[1].rm_eo - pm[1].rm_so, str + offset + pm[1].rm_so,
        pm[2].rm_eo - pm[2].rm_so, str + offset + pm[2].rm_so);
    
    offset += pm[0].rm_eo; // 移動到下一個匹配位置
    if (pm[0].rm_so == pm[0].rm_eo) offset++; // 防止零長度匹配死循環
}
regfree(®);

六、PCRE 庫:更強大的選擇

POSIX Regex 功能有限(不支持非貪婪、前瞻/後顧、遞歸等高級特性)。生產環境推薦 PCRE (Perl Compatible Regular Expressions):

  • 語法兼容 Perl 5,功能極其豐富
  • API:pcre_compile/pcre_exec/pcre_free
  • 支持 JIT 編譯極速匹配
  • C++ 封裝:pcrecpp、boost::regex、std::regex (C++11)

七、性能優化建議

  1. 預編譯重用:正則表達式編譯開銷大,編譯一次多次匹配
  2. 避免過度捕獲:不需要子表達式時用 REG_NOSUB 或非捕獲組 (?:...)
  3. 錨定優化:盡可能用 ^、$、\b 錨定,減少回溯
  4. 簡化模式:避免嵌套量詞 (a*)*、交替分支過多 (a|b|c|d|e)*
  5. 預編譯常用模式:啟動時編譯常用正則,存儲 regex_t 對象

八、總結

POSIX Regex API (regcomp/regexec/regfree/regerror) 為 C 語言提供了標準化的正則表達式處理能力。掌握編譯、匹配、捕獲組提取、錯誤處理、資源釋放全流程,配合 REG_EXTENDED 等標誌靈活控制行為,即可在 C 程序中實現類 Perl 的靈活字符串處理。對於高性能、高級特性需求,升級至 PCRE 庫是最佳選擇。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved