字符串处理进阶:查找、替换、分割、拼接与转换
字符串处理,说白了就是C语言里最磨人的那部分。我刚开始学的时候,总觉得指针都搞明白了,字符串还能难到哪去?结果第一次做项目,就被一个字符串分割的bug折腾了整整一个下午。嗯,从那以后我再也不敢小看这些函数了。
今天咱们就把字符串处理的核心操作捋一遍。查找、替换、分割、拼接,再加上字符串和数字的互相转换。这些技能,你工作中天天都会用到。
字符串查找:strstr 与 strchr
查找操作,说白了就是在字符串里找东西。找字符用 strchr,找子串用 strstr。这两个函数我几乎每天都会用到。
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "Hello, world! Welcome to C programming.";
char *p;
// 查找字符
p = strchr(str, 'w');
if (p) {
printf("找到字符 'w',位置:%ld\n", p - str);
}
// 查找子串
p = strstr(str, "world");
if (p) {
printf("找到子串 \"world\",位置:%ld\n", p - str);
}
return 0;
}
我的小经验:strstr返回的是指针,不是下标。想拿到位置,用指针减数组首地址就行。我在项目中经常用这个技巧做日志关键词过滤。
字符串替换:自己动手实现
C标准库没有直接提供替换函数。为什么?因为替换操作太灵活了——替换一次还是全部替换?替换后字符串变长了怎么办?这些都得你自己决定。
我个人习惯写一个通用的替换函数,支持指定替换次数:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// 替换字符串,返回新字符串
char *str_replace(const char *src, const char *old, const char *new, int count) {
char *result;
int i, cnt = 0;
int new_len = strlen(new);
int old_len = strlen(old);
// 先统计需要替换的次数
for (i = 0; src[i] != '\0'; ) {
if (strstr(&src[i], old) == &src[i]) {
cnt++;
if (cnt == count) break;
i += old_len;
} else {
i++;
}
}
// 分配内存
result = (char *)malloc(strlen(src) + cnt * (new_len - old_len) + 1);
if (!result) return NULL;
// 执行替换
int j = 0;
for (i = 0; src[i] != '\0'; ) {
if (cnt > 0 && strstr(&src[i], old) == &src[i]) {
strcpy(&result[j], new);
j += new_len;
i += old_len;
cnt--;
} else {
result[j++] = src[i++];
}
}
result[j] = '\0';
return result;
}
我曾经踩过的坑:替换后字符串可能变长,一定要动态分配内存。我见过有人用固定大小的缓冲区,结果替换几次就溢出了。嗯,那种bug特别难查。
字符串分割:strtok 的陷阱
分割字符串,大家第一个想到的就是 strtok。但这个函数有个大坑——它会修改原字符串。你想想看,如果你传了一个字符串常量进去,程序直接崩溃。
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "apple,banana,orange,grape";
char *token;
token = strtok(str, ",");
while (token) {
printf("%s\n", token);
token = strtok(NULL, ",");
}
return 0;
}
注意看,第一次调用传的是字符串本身,后面传的是NULL。这个设计其实挺巧妙的——函数内部用静态变量记住了上次的位置。但这也意味着,你不能在嵌套循环里同时用strtok分割两个字符串。
我的建议:如果你需要线程安全,或者不想破坏原字符串,用 strtok_r(可重入版本)。它多了一个参数来保存上下文。我在嵌入式项目里一直用这个。
字符串拼接:strcat 与 snprintf
拼接字符串,最简单的是 strcat。但说实话,我很少用它。为什么?因为它不检查缓冲区大小。你拼接的时候如果目标缓冲区不够大,直接就溢出了。
我个人更推荐用 snprintf:
#include <stdio.h>
#include <string.h>
int main() {
char buf[64] = "Hello";
char *name = "Alice";
char *greeting = "Good morning";
// 安全的拼接方式
int len = strlen(buf);
snprintf(buf + len, sizeof(buf) - len, ", %s! %s", name, greeting);
printf("%s\n", buf); // 输出:Hello, Alice! Good morning
return 0;
}
核心原则:永远不要相信输入的长度。用snprintf代替strcat,用strncpy代替strcpy。这个习惯能帮你避免90%的缓冲区溢出问题。
字符串与数字的转换
这部分在实际项目中太常用了。从配置文件读到的都是字符串,但你要当数字用。反过来,你要把数字打印成字符串显示出来。
atoi / atof:简单但危险
#include <stdio.h>
#include <stdlib.h>
int main() {
char *num_str = " -42";
char *float_str = "3.14159";
char *bad_str = "abc123";
int i = atoi(num_str);
double d = atof(float_str);
printf("整数:%d\n", i);
printf("浮点数:%f\n", d);
// 注意:atoi无法检测错误
int bad = atoi(bad_str);
printf("错误输入的结果:%d\n", bad); // 返回0,但你不知道是转换失败还是真的0
return 0;
}
我曾经犯过的错:用atoi解析用户输入,结果用户输了个"abc",程序没报错直接返回0。后来查了半天才发现是输入格式问题。现在我改用strtol和strtof了,它们能告诉你哪里出错了。
strtol / strtof:安全的替代方案
#include <stdio.h>
#include <stdlib.h>
int main() {
char *str = " 123abc";
char *endptr;
long val = strtol(str, &endptr, 10);
if (endptr == str) {
printf("转换失败:没有找到数字\n");
} else if (*endptr != '\0') {
printf("部分转换成功:%ld,剩余部分:%s\n", val, endptr);
} else {
printf("完全转换成功:%ld\n", val);
}
return 0;
}
你看,strtol 通过 endptr 告诉你转换停在了哪里。如果 endptr 和输入指针一样,说明根本没转换到数字。这个信息太有用了。
sprintf / snprintf:数字转字符串
#include <stdio.h>
int main() {
char buf[32];
int num = 255;
double pi = 3.1415926;
// 整数转字符串
snprintf(buf, sizeof(buf), "%d", num);
printf("整数转字符串:%s\n", buf);
// 浮点数转字符串(控制精度)
snprintf(buf, sizeof(buf), "%.2f", pi);
printf("浮点数转字符串(保留2位小数):%s\n", buf);
// 十六进制
snprintf(buf, sizeof(buf), "0x%X", num);
printf("十六进制:%s\n", buf);
return 0;
}
sscanf:从字符串中解析数据
#include <stdio.h>
int main() {
char *data = "Alice 25 175.5";
char name[32];
int age;
float height;
int ret = sscanf(data, "%s %d %f", name, &age, &height);
if (ret == 3) {
printf("姓名:%s,年龄:%d,身高:%.1f\n", name, age, height);
} else {
printf("解析失败,成功匹配了%d个字段\n", ret);
}
return 0;
}
我的使用习惯:sscanf特别适合解析格式固定的日志文件。我在一个项目中用它解析传感器数据,一行代码就搞定了原本需要写十几行的字符串分割逻辑。
知识体系总览
下面这张图把字符串处理的核心操作串起来了。你仔细看看,每个操作之间其实是有联系的——比如分割之后往往需要转换,拼接之前可能需要先替换。
总结一下
字符串处理这块,说白了就几个要点:
- 查找用strstr和strchr,记住返回的是指针
- 替换没有标准函数,自己写的时候注意内存分配
- 分割用strtok要小心原字符串被修改,多线程用strtok_r
- 拼接优先用snprintf,安全又灵活
- 转换别用atoi,用strtol和strtof,它们能告诉你转换结果是否有效
这些函数我每天都在用。你刚开始可能会觉得记不住,没关系,写几个小例子跑一遍就熟了。遇到问题的时候,多想想边界情况——空字符串怎么办?输入太长怎么办?格式不对怎么办?把这些想清楚了,你的代码就会稳很多。