字符串处理进阶:从解析到搜索,C语言字符串三板斧
字符串处理,说白了就是C语言里最磨人、也最见功底的一块。我做了这么多年嵌入式开发,几乎每个项目都离不开字符串的解析和查找。今天咱们就聊聊三个核心工具:字符串转数字、字符串分割、字符串查找。掌握了它们,面试官再问字符串相关的问题,你心里就有底了。
一、字符串转数字:atoi、atol、strtol
把字符串变成数字,这事儿看起来简单,但坑不少。我见过太多新手直接用 atoi,结果遇到非法输入直接崩了。
1. atoi 和 atol:简单但危险
atoi 把字符串转成 int,atol 转成 long。用法很简单:
#include <stdlib.h>
#include <stdio.h>
int main() {
char str1[] = "1234";
char str2[] = " -567";
char str3[] = "abc"; // 非法输入
int a = atoi(str1); // a = 1234
int b = atoi(str2); // b = -567
int c = atoi(str3); // c = 0,但无法区分是"0"还是错误
printf("%d %d %d\n", a, b, c);
return 0;
}
⚠️ 注意:atoi 遇到非法字符直接返回 0,你根本不知道是输入了 "0" 还是输入了 "abc"。我在项目中吃过这个亏——用户输入了一个字母,程序没报错,但数据全乱了。
2. strtol:我推荐你用它
我个人习惯用 strtol,它能检测错误,还能指定进制。看代码:
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
int main() {
char str[] = " 1234abc";
char *endptr;
errno = 0;
long val = strtol(str, &endptr, 10);
if (errno != 0) {
printf("转换出错\n");
} else if (*endptr != '\0') {
printf("部分转换成功,未转换部分: %s\n", endptr);
printf("转换结果: %ld\n", val);
} else {
printf("完全转换: %ld\n", val);
}
return 0;
}
endptr 指向第一个未转换的字符。如果 *endptr == '\0',说明全部转换成功。这招我在解析配置文件时经常用,能精准定位错误位置。
💡 小技巧:strtol 的第三个参数可以指定进制。2 到 36 都行。传 0 的话,它会自动识别:0x 开头是十六进制,0 开头是八进制,其他是十进制。
二、字符串分割:strtok 的妙用
分割字符串,C 标准库给了我们 strtok。但它有个特点——会修改原字符串。嗯,这里要注意。
1. strtok 的基本用法
#include <string.h>
#include <stdio.h>
int main() {
char str[] = "apple,banana,orange,grape";
char *token;
token = strtok(str, ",");
while (token != NULL) {
printf("%s\n", token);
token = strtok(NULL, ",");
}
return 0;
}
输出:
apple
banana
orange
grape
第一次调用传原字符串,后面传 NULL。为什么?因为 strtok 内部维护了一个静态指针,记住上次的位置。这既是优点也是缺点——多线程环境下会出问题。
⚠️ 注意:strtok 不是线程安全的。如果你在多线程程序里用它,两个线程同时调用,内部静态指针会被覆盖。我曾经在一个网络服务器项目里踩过这个坑,排查了两天才找到原因。
2. 线程安全版本:strtok_r
POSIX 标准提供了 strtok_r,多传一个指针参数保存状态:
#include <string.h>
#include <stdio.h>
int main() {
char str[] = "apple,banana,orange,grape";
char *token;
char *saveptr;
token = strtok_r(str, ",", &saveptr);
while (token != NULL) {
printf("%s\n", token);
token = strtok_r(NULL, ",", &saveptr);
}
return 0;
}
你看,多了一个 saveptr 参数,每个线程传自己的指针,互不干扰。我建议你在任何可能涉及多线程的场景下,都用 strtok_r。
三、字符串查找:strstr 和 strchr
查找子串或字符,这两个函数是标配。
1. strstr:查找子串
#include <string.h>
#include <stdio.h>
int main() {
char str[] = "Hello, welcome to C world!";
char *pos;
pos = strstr(str, "welcome");
if (pos != NULL) {
printf("找到了,位置偏移: %ld\n", pos - str);
printf("从该位置开始的字符串: %s\n", pos);
} else {
printf("没找到\n");
}
return 0;
}
返回的是指针,不是索引。想算索引?用指针减数组首地址就行。我在做日志分析工具时,经常用 strstr 来过滤关键字。
2. strchr 和 strrchr:查找字符
strchr 从左往右找,strrchr 从右往左找:
#include <string.h>
#include <stdio.h>
int main() {
char str[] = "Hello, world! Hello, C!";
char *first = strchr(str, 'H');
char *last = strrchr(str, 'H');
if (first) printf("第一个 H 在: %ld\n", first - str);
if (last) printf("最后一个 H 在: %ld\n", last - str);
return 0;
}
输出:
第一个 H 在: 0
最后一个 H 在: 14
💡 小技巧:想找文件路径里的文件名?用 strrchr 找最后一个 '/' 或 '\',后面的就是文件名。这个技巧我在写文件管理工具时经常用。
四、知识体系总览
下面这张图帮你理清这三个工具的关系和使用场景:
五、避坑指南
最后,分享几个我踩过的坑:
- atoi 的返回值陷阱:我曾经用 atoi 解析用户输入,用户输入了 "abc",程序没报错,但后续计算全错了。后来全部换成 strtol,问题解决。
- strtok 修改原串:如果你需要保留原字符串,记得先拷贝一份。我在解析配置文件时,因为忘了这点,导致原始数据丢失,排查了半天。
- strstr 的返回值:返回的是指针,不是布尔值。别写成
if (strstr(str, "abc")),虽然也能用,但语义不清晰。我习惯写成if (strstr(str, "abc") != NULL)。 - 多线程下的 strtok:这个坑我印象最深。一个网络服务程序,两个线程同时解析不同的字符串,结果数据互相污染。换成 strtok_r 后一切正常。
字符串处理看起来简单,但细节决定成败。把这些函数用熟了,你的 C 语言水平会上一个台阶。
公众号:蓝海资料掘金营,微信deep3321