字符串处理进阶:从解析到搜索,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 找最后一个 '/' 或 '\',后面的就是文件名。这个技巧我在写文件管理工具时经常用。

四、知识体系总览

下面这张图帮你理清这三个工具的关系和使用场景:

C语言字符串处理进阶 字符串转数字 atoi / atol 简单但无错误检测 strtol(推荐) 完整错误处理,支持进制 字符串分割 strtok 非线程安全,修改原串 strtok_r(推荐) 线程安全版本 字符串查找 strstr 查找子串 strchr / strrchr 查找字符(正向/反向) 核心要点总结 1. 数字转换优先用 strtol,别用 atoi 2. 字符串分割用 strtok_r,注意原串会被修改 3. 查找子串用 strstr,查找字符用 strchr/strrchr 4. 所有函数返回指针,用指针减首地址算索引

五、避坑指南

最后,分享几个我踩过的坑:

  • atoi 的返回值陷阱:我曾经用 atoi 解析用户输入,用户输入了 "abc",程序没报错,但后续计算全错了。后来全部换成 strtol,问题解决。
  • strtok 修改原串:如果你需要保留原字符串,记得先拷贝一份。我在解析配置文件时,因为忘了这点,导致原始数据丢失,排查了半天。
  • strstr 的返回值:返回的是指针,不是布尔值。别写成 if (strstr(str, "abc")),虽然也能用,但语义不清晰。我习惯写成 if (strstr(str, "abc") != NULL)
  • 多线程下的 strtok:这个坑我印象最深。一个网络服务程序,两个线程同时解析不同的字符串,结果数据互相污染。换成 strtok_r 后一切正常。

字符串处理看起来简单,但细节决定成败。把这些函数用熟了,你的 C 语言水平会上一个台阶。


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