C语言菜鸟入门·字符串分割函数·strtok ()

该文章已生成可运行项目,

目录

1.  函数简介

2.  函数原型

3.  用法示例

4.  使用场景

4.1 文本解析(CSV/TSV处理)

4.2 命令行参数解析

4.3 字符串重构

4.4  日志分析

4.5 数据格式转换

5.  注意事项

5.1 修改原字符串

5.2 分隔符的隐蔽陷阱

5.3 连续分隔符


1.  函数简介

        strtok 是 C 语言标准库中的字符串分割函数,用于将字符串按照指定的分隔符拆分成多个子字符串(token)。每次调用 strtok 时,它会返回指向下一个标记的指针,这个标记是从字符串中提取出的、不包含任何分隔符的连续字符序列。它是处理文本数据(如 CSV、日志文件等)的常用工具。

2.  函数原型

#include <string.h>

char *strtok(char *str, const char *delimiters);
  • str:待分割的字符串(首次调用时传入),在后续的调用中,为了继续从上次停止的地方分割剩余的字符串,str 应被设置为 NULL。
  • delimiters:分隔符集合(字符串)
  • 成功:返回下一个分割出的子字符串的指针
  • 结束:当没有更多子字符串时返回 NULL

在使用过程中需要注意一下几点:

① 由于 strtok 会修改原始字符串,使用时需特别小心。会修改原始字符串(将分隔符替换为 \0);

② 内部使用静态变量记录当前分割位置;

③ 在处理连续分割符时,会自动跳过连续的分隔符;

④ 多线程环境中应使用 strtok_r。

3.  用法示例

        举一个简单的例子:

#include <stdio.h>
#include <string.h>

int main() {
    char text[] = "apple,banana;orange peach"; // 必须可修改(数组而非指针)
    const char *delim = ",; "; // 分隔符:逗号、分号、空格
    
    // 首次调用传入字符串
    char *token = strtok(text, delim);
    
    while (token != NULL) {
        printf("Token: %s\n", token);
        // 后续调用传入 NULL 继续分割
        token = strtok(NULL, delim);
    }
    
    return 0;
}

工作原理我们来解释一下:

原始字符串: "apple,banana;orange peach"
                    ▲ 初始位置

第1次调用: 找到第一个分隔符 ',' 替换为 '\0'
                  返回 "apple" 的地址
                 内部指针移动到 'b'

第2次调用: 找到 ';' 替换为 '\0'
                  返回 "banana" 的地址
                  内部指针移动到 'o'

第3次调用: 找到 ' ' 替换为 '\0'
                 返回 "orange" 地址
                 内部指针移动到 'p'

第4次调用: 到字符串结尾
                 返回 "peach" 地址

第5次调用: 返回 NULL

4.  使用场景

4.1 文本解析(CSV/TSV处理)

        解析逗号/制表符分隔的文本数据,高效处理不规则分隔(如连续分隔符),可以使用 strtok 函数将每行数据按逗号分割成多个字段。举个例子:

char line[] = "2023-08-20,42.5,78%,Sunny";
const char *delim = ",";
char *date = strtok(line, delim);
char *temp = strtok(NULL, delim);  // 继续分割
char *humidity = strtok(NULL, delim);
char *weather = strtok(NULL, delim);

// 处理各字段
printf("日期: %s, 温度: %s℃\n", date, temp);

4.2 命令行参数解析

        处理复杂命令行参数(如键值对参数),举个例子,如解析解析-config key1=value1;key2=value2格式:

char args[] = "-config timeout=30;retry=3";
char *config_part = strtok(args, " "); // 先按空格分割
if (config_part && strcmp(config_part, "-config") == 0) {
    char *params = strtok(NULL, "");
    char *pair = strtok(params, ";");  // 再按分号分割键值对
    while (pair) {
        char *key = strtok(pair, "=");
        char *value = strtok(NULL, "=");
        printf("配置项: %s -> %s\n", key, value);
        pair = strtok(NULL, ";");
    }
}

4.3 字符串重构

        提取字符串片段重组新字符串,例如在URL提取域名和路径:

char url[] = "https://www.example.com/path/to/page";
strtok(url, "://");  // 跳过协议头
char *domain = strtok(NULL, "/");
char *path = strtok(NULL, "");  // 剩余部分

char new_path[100];
snprintf(new_path, sizeof(new_path), "DOMAIN: %s\nPATH: /%s", domain, path);

4.4  日志分析

        在处理日志文件时,日志项可能由特定的分隔符分隔(如空格、逗号、制表符等)。strtok 可以用来将这些日志项分割成单独的条目,以便进行进一步的分析或处理。例如分析Nginx日志:

// 示例日志:127.0.0.1 - - [20/Aug/2023:10:12:33] "GET /index.html HTTP/1.1" 200 1532
char log[] = "127.0.0.1 - - [20/Aug/2023:10:12:33] \"GET /index.html HTTP/1.1\" 200 1532";

char *ip = strtok(log, " ");      // 客户端IP
strtok(NULL, " [");               // 跳过无用字段
char *time = strtok(NULL, "]");   // 提取时间
strtok(NULL, "\"");               // 定位到请求
char *request = strtok(NULL, "\""); // 完整请求
strtok(NULL, " ");
char *status = strtok(NULL, " "); // 状态码
char *size = strtok(NULL, " ");   // 响应大小

printf("IP:%s 在 %s 请求 %s\n", ip, time, request);

4.5 数据格式转换

        不同格式间的数据转换,可以配合配合sprintf实现格式重组,举个例子,将日期格式转换(YYYY-MM-DD → DD/MM/YYYY):

char date[] = "2023-08-20";
char *year = strtok(date, "-");
char *month = strtok(NULL, "-");
char *day = strtok(NULL, "-");

char new_date[20];
sprintf(new_date, "%s/%s/%s", day, month, year); // 20/08/2023

5.  注意事项

5.1 修改原字符串

        strtok 会直接修改原始字符串,将分隔符位置替换为 \0。这种设计虽然节省内存,但会导致原始数据不可逆损坏。举一个错误的示例:

char config[] = "key1=value1;key2=value2";
char *backup = config;  // 错误!只是指针复制

strtok(config, ";");    // 原始config已被修改
// 此时backup同样指向被破坏的字符串

        正确的做法,可以使用 strdup 进行拷贝数据,然后对拷贝的数据进行处理:

char config[] = "key1=value1;key2=value2";
char *copy = strdup(config);  // 深度拷贝
if (!copy) { /* 处理内存不足 */ }

// 安全操作副本
strtok(copy, ";");
free(copy);  // 记得释放

strdup的用法可以参考:

C语言菜鸟入门·浅析strdup和strcpy的区别-优快云博客

5.2 分隔符的隐蔽陷阱

        在使用特殊字符时,最好显示调用,否则可能匹配不到:

// 看似正常的分割
char *token = strtok(path, "/\\");  // 试图匹配/或\

// 但当遇到如下字符串时:
char path[] = "C:\\Program Files/App";
// 实际分割结果可能不符合预期

        最佳做法:

// 显式定义分隔符集合
const char *DELIMITERS = ",; \t\n\r";
char *token = strtok(input, DELIMITERS);

5.3 连续分隔符

        保留空字段的方法:

char data[] = "1,,3";
char *results[3];
int count = 0;

char *ptr = data;
while (count < 3) {
    results[count] = (*ptr != '\0') ? ptr : "(empty)";
    ptr = strtok(ptr, ",");
    if (ptr) ptr += strlen(ptr) + 1;
    count++;
}
// 结果: ["1", "(empty)", "3"]

C语言_时光の尘的博客-优快云博客

本文章已经生成可运行项目
<think>嗯,用户想了解Boyer-Moore算法中的坏字符规则。首先,我需要回忆一下BM算法的基本概念。BM算法是一种高效的字符串匹配算法,主要依赖于坏字符规则和好后缀规则两种启发式方法。用户的问题集中在坏字符规则上,所以我要详细解释这部分。 坏字符规则的基本思想是,当模式串和文本中的某个字符不匹配时,可以将模式串向右滑动尽可能多的位数,以提高匹配效率。这里要注意的是,坏字符指的是文本中与模式串当前位置不匹配的那个字符。例如,当模式串的某个字符在文本中对应位置不匹配时,这个文本中的字符就是坏字符。 接下来,我需要解释坏字符规则的具体步骤。首先,预处理阶段需要为模式串中的每个字符记录其最后出现的位置。这一步是为了在遇到坏字符时,能够快速查找模式串中该字符最后出现的位置,从而确定滑动的距离。例如,如果模式串是"EXAMPLE",那么每个字符的最后位置会被记录下来,如'E'在位置6,'X'在位置1,等等。 然后,在匹配过程中,当发现不匹配时,根据坏字符在模式串中的最后出现位置来计算滑动距离。如果坏字符在模式串中存在,滑动距离是当前位置减去坏字符在模式串中的最后位置;如果坏字符不存在于模式串中,则直接滑动整个模式串的长度。不过,这里需要注意滑动距离不能是负数,所以可能需要向右滑动一位的情况,比如当坏字符在模式串中最后出现的位置比当前不匹配的位置还要靠右的时候。 举个例子来说明会更清楚。比如文本是"ABCDABEACADEMYEXAMPLE",模式串是"EXAMPLE"。当从右向左匹配时,假设在某个位置发现不匹配,比如模式串的第三个字符'M'与文本中的某个字符不匹配,而坏字符是文本中的某个字符。根据预处理的结果,查找坏字符在模式中的最后位置,然后滑动模式串,使得该位置对齐。 不过,用户可能会问为什么有时候滑动距离会是负数,这时候应该怎么处理。这时候需要说明,当坏字符在模式串中的最后出现位置在当前位置右侧时,滑动距离可能为负,此时应该向右滑动一位,避免模式串左移,导致错过可能的匹配。 此外,坏字符规则虽然高效,但也有局限性。比如,在某些情况下,坏字符规则可能不如好后缀规则有效,或者两者结合使用才能达到最佳效果。但用户的问题主要关注坏字符规则,所以这部分可以简要提及,但不深入。 最后,需要确保解释清晰,步骤明确,并且有具体的例子帮助理解。同时,根据用户提供的引用,特别是引用[3]和引用[4],可以引用相关的内容来支持解释,比如提到BM算法的高效性和应用场景,以及Horspool对出现启发式规则的贡献。</think>### Boyer-Moore算法中的坏字符规则 Boyer-Moore(BM)算法通过**坏字符规则**和**好后缀规则**两种启发式策略提升字符串匹配效率。坏字符规则的核心思想是**利用不匹配的字符信息快速滑动模式串**,减少不必要的比较次数。 --- #### 坏字符规则详解 1. **预处理阶段** 为模式串中每个字符记录其**最后出现的位置**(从右向左扫描)。若字符重复出现,保留最右侧的位置。例如,模式串为 `EXAMPLE`: - `E` 最后出现在位置6 - `X` 最后出现在位置1 - 其他字符(如 `A`, `M`, `P`, `L`, `E`)同理。 2. **匹配阶段** 当文本与模式串的某个位置发生不匹配时: - **坏字符**:文本中导致不匹配的字符(记为 `c`)。 - **滑动距离**:根据 `c` 在模式串中的最后位置计算。 - 若 `c` 存在于模式串中,滑动距离为 `j - bc[c]`(`j` 是模式串中当前不匹配的位置索引)。 - 若 `c` 不在模式串中,滑动整个模式串长度 `m`。 **注意**:若滑动距离为负数(即 `bc[c] > j`),则模式串需向右移动1位以避免回溯[^3][^4]。 --- #### 示例 **文本**:`ABCDABEACADEMYEXAMPLE` **模式串**:`EXAMPLE` **预处理**:`bc['E']=6`, `bc['X']=1`, 其他字符位置略。 1. 初始对齐位置: ``` ABCDA BEACADEMYEXAMPLE EXAMPLE ``` 2. 从右向左匹配时,发现第3个字符不匹配(假设文本中对应字符为 `B`)。 3. 查找 `B` 在模式串中的最后位置(未找到),滑动整个模式串长度7位。 4. 若坏字符为 `A` 且 `bc['A']=2`,滑动距离为 `j - bc['A'] = 3 - 2 = 1`,即右移1位。 --- #### 性能分析 - **优势**:坏字符规则可跳过大量字符,尤其在字符集较大时效果显著(如英文文本)。 - **局限性**:单独使用时可能出现滑动距离不足,需结合**好后缀规则**优化[^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

时光の尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值