字符串查找与分割:strstr、strchr、strtok 的用法
字符串处理,说白了就是在一堆字符里找到你想要的东西,或者把它拆成几段。C 语言标准库给了我们三个好用的工具:strstr、strchr 和 strtok。今天我就带你把它们彻底搞明白,最后再手写一个简单的分割函数。
1. strstr:找子串
strstr 的作用很简单——在一个字符串里找另一个字符串。找到了,返回第一次出现的位置;找不到,返回 NULL。
#include <string.h>
char *strstr(const char *haystack, const char *needle);
我在项目中遇到过这样一个场景:解析 HTTP 请求头时,需要判断是否包含 "Content-Type" 字段。用 strstr 一行代码就搞定了。
核心要点:
- 区分大小写,别指望它能帮你忽略大小写
- 如果 needle 是空字符串,返回 haystack 本身
- 返回的是指针,你可以用它做后续操作
char url[] = "https://example.com/api/v1/users";
char *pos = strstr(url, "api");
if (pos) {
printf("找到了!位置:%ld\n", pos - url);
}
2. strchr:找字符
strchr 比 strstr 更轻量,它只找一个字符。你想想看,很多时候我们只需要定位一个分隔符,比如逗号、冒号、空格,这时候用 strchr 就够了。
char *strchr(const char *s, int c);
嗯,这里要注意:第二个参数虽然是 int 类型,但你传 char 就行,它会自动转换。我曾经见过有人传了 0,结果一直返回 NULL——因为 0 就是 '\0',字符串结尾嘛。
小技巧:想从右往左找?用 strrchr,它从字符串末尾开始搜索。
char path[] = "/usr/local/bin/gcc";
char *last_slash = strrchr(path, '/');
if (last_slash) {
printf("文件名:%s\n", last_slash + 1); // 输出 "gcc"
}
3. strtok:字符串分割利器
strtok 是个狠角色。它能把字符串按分隔符切成一段一段的。但用起来有点坑,我慢慢说。
char *strtok(char *str, const char *delim);
它的工作机制是这样的:第一次调用时传原始字符串,后面再调用传 NULL,它会记住上次切到哪了。说白了,它内部维护了一个静态指针。
警告:strtok 会修改原字符串!它会把你传的分隔符替换成 '\0'。如果你需要保留原字符串,先拷贝一份。
char data[] = "192.168.1.1,80,HTTP";
char *token;
token = strtok(data, ",");
while (token != NULL) {
printf("%s\n", token);
token = strtok(NULL, ",");
}
// 输出:
// 192.168.1.1
// 80
// HTTP
我曾经在解析配置文件时吃过这个亏:第一次调用后原字符串就被改了,后面想再用原数据就全乱了。所以我的建议是——永远先拷贝一份再切。
4. 手写一个简单的字符串分割函数
标准库的 strtok 有个问题:它不是线程安全的。因为内部用了静态变量,多线程环境下会互相干扰。虽然 Linux 下有 strtok_r(可重入版本),但咱们自己写一个也不难。
#include <stdio.h>
#include <string.h>
// 自定义分割函数:按单个字符分割
// 返回分割后的子串数量
int split_string(const char *input, char delimiter,
char result[][100], int max_tokens) {
int count = 0;
const char *start = input;
const char *end;
while (*start && count < max_tokens) {
// 跳过开头的分隔符
while (*start == delimiter) start++;
if (!*start) break;
// 找下一个分隔符
end = strchr(start, delimiter);
if (end == NULL) {
// 最后一个子串
strncpy(result[count], start, 99);
result[count][99] = '\0';
count++;
break;
}
// 复制子串
int len = end - start;
if (len > 99) len = 99;
strncpy(result[count], start, len);
result[count][len] = '\0';
count++;
start = end + 1;
}
return count;
}
int main() {
char input[] = "apple,banana,,,grape,orange";
char tokens[10][100];
int count = split_string(input, ',', tokens, 10);
for (int i = 0; i < count; i++) {
printf("Token %d: %s\n", i, tokens[i]);
}
return 0;
}
这个函数有几个好处:
- 不修改原字符串——我用的是 const char *,安全第一
- 线程安全——所有状态都在栈上,没有静态变量
- 能处理连续分隔符——比如 "a,,b" 会跳过中间的空串
避坑指南:我曾经在嵌入式项目里用 strtok 解析 GPS 数据,结果因为中断服务函数里也调用了 strtok,导致数据全乱了。从那以后,但凡涉及多线程或中断,我都自己写分割函数。
5. 知识体系总览
下面这张图帮你理清这三个函数的关系和适用场景:
6. 总结一下
这三个函数各有各的脾气:
- strstr:找子串,不破坏原数据,适合做存在性检查
- strchr:找字符,轻量快速,适合定位分隔符
- strtok:分割利器,但会改原串,用之前一定先拷贝
我个人习惯是:能不用 strtok 就不用,除非是快速写个测试脚本。正式项目里,我宁愿多写几行代码自己实现分割,换来的是可控和安全。你想想看,代码多写几行没关系,线上出 bug 才真要命。
最后一个小建议:如果你在嵌入式或实时系统里工作,尽量避开 strtok。用我上面写的 split_string 函数,或者用 strtok_r,省心很多。
公众号:蓝海资料掘金营,微信deep3321