字符串处理进阶:查找、替换、分割、拼接与转换

字符串处理,说白了就是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特别适合解析格式固定的日志文件。我在一个项目中用它解析传感器数据,一行代码就搞定了原本需要写十几行的字符串分割逻辑。

知识体系总览

下面这张图把字符串处理的核心操作串起来了。你仔细看看,每个操作之间其实是有联系的——比如分割之后往往需要转换,拼接之前可能需要先替换。

字符串处理 查找 替换 分割 拼接 数字转换 strchr / strstr 手动实现(无标准库) strtok / strtok_r strcat / snprintf atoi / strtol / sprintf / sscanf

总结一下

字符串处理这块,说白了就几个要点:

  • 查找用strstr和strchr,记住返回的是指针
  • 替换没有标准函数,自己写的时候注意内存分配
  • 分割用strtok要小心原字符串被修改,多线程用strtok_r
  • 拼接优先用snprintf,安全又灵活
  • 转换别用atoi,用strtol和strtof,它们能告诉你转换结果是否有效

这些函数我每天都在用。你刚开始可能会觉得记不住,没关系,写几个小例子跑一遍就熟了。遇到问题的时候,多想想边界情况——空字符串怎么办?输入太长怎么办?格式不对怎么办?把这些想清楚了,你的代码就会稳很多。


公众号:蓝海资料掘金营,微信 deep3321