C 库函数 – strtok()(超详细)

C 库函数 – strtok() 的实用指南

在 C 语言的字符串处理中,有一个常被忽视但极其强大的函数:strtok()。它属于标准库 <string.h> 中的函数之一,功能是将一个字符串按指定的分隔符“切分”成多个子串。如果你正在处理用户输入、配置文件解析、日志分析,或者需要从一行文本中提取关键词,strtok() 就是你该用的工具。

很多初学者在面对字符串拆分时,会用循环遍历字符、手动判断是否是分隔符,写出来代码冗长又容易出错。而 strtok() 就像是一个“智能切片刀”,只需告诉它“切哪里”,它就能自动帮你把字符串切成一块块“小段”。

接下来,我们就从基础用法、常见陷阱、实战案例,再到高级技巧,一步步带你掌握这个实用函数。


函数原型与基本用法

strtok() 的原型如下:

char *strtok(char *str, const char *delim);
  • str:要被切分的原始字符串。第一次调用时传入原始字符串,后续调用传 NULL。
  • delim:分隔符字符串,比如 " ,\t\n" 表示空格、逗号、制表符、换行符都作为分隔符。
  • 返回值:指向当前被切分出的子串的指针。当没有更多子串时,返回 NULL。

使用要点

  • strtok() 是“状态机”函数:它内部维护一个指针,记录上次切分的位置。因此,第二次调用时必须传 NULL,不能传原始字符串。
  • 它会修改原始字符串:strtok() 会将分隔符替换为 '\0',从而实现“切分”。所以原始字符串不能是常量字符串(如字符串字面量),否则会导致程序崩溃。
  • 线程不安全:因为使用了内部静态变量,多线程下使用会出问题。如有需要,应使用线程安全版本 strtok_r()。

实际案例:解析命令行参数

假设我们有一个字符串,内容是用户输入的一行命令,比如:

ls -l -a /home/user

我们想把它拆分成各个参数,方便后续处理。代码如下:

#include <stdio.h>
#include <string.h>

int main() {
    // 原始字符串,注意:不能是字符串字面量!
    char input[] = "ls -l -a /home/user";
    
    // 定义分隔符:空格
    const char *delim = " ";
    
    // 第一次调用,传入原始字符串
    char *token = strtok(input, delim);
    
    // 循环取出每个子串
    while (token != NULL) {
        printf("参数: %s\n", token);
        // 后续调用必须传 NULL,让 strtok 继续从上次断点开始
        token = strtok(NULL, delim);
    }
    
    return 0;
}

输出结果:

参数: ls
参数: -l
参数: -a
参数: /home/user

关键注释

  • char input[] = "..." 说明 input 是可修改的数组,不是常量字符串。
  • 第一次调用 strtok(input, delim) 时,函数会从头开始扫描,找到第一个空格,替换为 '\0',返回 "ls"。
  • 第二次调用 strtok(NULL, delim),strtok() 会从上次断点继续,找到下一个空格,返回 "-l"。
  • 当没有更多子串时,strtok() 返回 NULL,循环结束。

常见陷阱与规避方法

陷阱 1:修改了原始字符串

strtok() 会把分隔符替换成 '\0',这虽然高效,但也意味着原始字符串被破坏。如果你还需要保留原始字符串,必须先复制一份。

char original[] = "apple,banana,grape";
char copy[100];
strcpy(copy, original);  // 复制一份

char *token = strtok(copy, ",");
while (token != NULL) {
    printf("水果: %s\n", token);
    token = strtok(NULL, ",");
}

⚠️ 如果你直接用 strtok("apple,banana,grape", ","),程序会崩溃,因为字符串字面量无法修改。


陷阱 2:分隔符顺序影响结果

如果分隔符中有多个字符,strtok() 会把它们都当作“分隔符”,但顺序会影响结果。例如:

char str[] = "a,,b,c";
char *delim = ",;";
char *token = strtok(str, delim);

while (token != NULL) {
    printf("token: %s\n", token);
    token = strtok(NULL, delim);
}

输出:

token: a
token: b
token: c

🔍 解释:strtok() 会把所有在 delim 中的字符都视为分隔符,包括 ;。所以 a,,b 中的两个逗号都被当作分隔符,中间空段被跳过。


多种分隔符的处理技巧

在实际应用中,我们常需要支持多种分隔符。比如 CSV 文件可能用逗号、分号或空格分隔。

#include <stdio.h>
#include <string.h>

int main() {
    char line[] = "name:张三,age:25;city:北京";
    
    // 支持多个分隔符:冒号、逗号、分号
    const char *delim = ":,;";
    
    char *token = strtok(line, delim);
    
    while (token != NULL) {
        printf("字段值: %s\n", token);
        token = strtok(NULL, delim);
    }
    
    return 0;
}

输出:

字段值: name
字段值: 张三
字段值: age
字段值: 25
字段值: city
字段值: 北京

✅ 这种方式非常适合解析配置项、日志行、表单数据等结构化文本。


用 strtok() 解析日志行的实战示例

假设我们有一行系统日志:

2024-04-05 14:23:12 ERROR User login failed for admin

我们想提取时间、日志级别、信息内容。

#include <stdio.h>
#include <string.h>

int main() {
    char log[] = "2024-04-05 14:23:12 ERROR User login failed for admin";
    
    // 分隔符:空格
    const char *delim = " ";
    
    char *token = strtok(log, delim);
    
    // 第一个 token 是日期
    printf("日期: %s\n", token);
    
    // 第二个是时间
    token = strtok(NULL, delim);
    printf("时间: %s\n", token);
    
    // 第三个是日志级别
    token = strtok(NULL, delim);
    printf("级别: %s\n", token);
    
    // 剩下的部分合并成信息内容
    char message[256] = "";
    while ((token = strtok(NULL, delim)) != NULL) {
        strcat(message, token);
        strcat(message, " ");
    }
    
    // 去掉末尾多余的空格
    if (strlen(message) > 0) {
        message[strlen(message) - 1] = '\0';
    }
    
    printf("信息: %s\n", message);
    
    return 0;
}

输出:

日期: 2024-04-05
时间: 14:23:12
级别: ERROR
信息: User login failed for admin

💡 这种写法在日志解析、CSV 处理、命令行工具中非常常见,是 C 语言处理文本的典型模式。


高级技巧:避免重复切分同一字符串

如果你需要多次解析同一个字符串(比如不同分隔符),就不能直接用 strtok(),因为它会修改原始字符串并依赖内部状态。

解决方法是:复制一份字符串,每次使用独立的副本

char original[] = "a,b;c:d";

// 第一次解析,用逗号分隔
char copy1[100];
strcpy(copy1, original);
char *token1 = strtok(copy1, ",");
while (token1 != NULL) {
    printf("第一轮: %s\n", token1);
    token1 = strtok(NULL, ",");
}

// 第二次解析,用分号分隔
char copy2[100];
strcpy(copy2, original);
char *token2 = strtok(copy2, ";");
while (token2 != NULL) {
    printf("第二轮: %s\n", token2);
    token2 = strtok(NULL, ";");
}

✅ 这样做虽然多复制了一次,但保证了每次解析都是独立的,不会互相干扰。


常见问题与调试建议

问题现象 可能原因 解决方法
程序崩溃 使用字符串字面量作为 str 参数 改为字符数组,如 char str[] = "..."
输出为空或只出一个 token 没有正确传入 NULL 确保后续调用传 NULL
无法读取最后一个子串 分隔符在末尾 检查字符串末尾是否有分隔符
多次调用结果不一致 未使用副本 每次解析使用独立副本

总结

C 库函数 – strtok() 是一个简单却功能强大的字符串切分工具。它通过修改原始字符串的方式实现高效切分,但使用时必须注意:

  • 原始字符串必须可修改,不能是常量;
  • 第一次调用传字符串,后续传 NULL;
  • 分隔符字符串中多个字符都被视为分隔符;
  • 多次解析需复制字符串,避免状态污染。

掌握这个函数,你就能轻松处理用户输入、配置文件、日志解析、CSV 数据等常见场景。虽然它不是最“现代”的方式(比如 C++ 的 stringstream 更安全),但在 C 语言中,strtok() 依然是最经典、最高效的字符串处理工具之一。

记住:工具本身不复杂,关键在于理解它的“状态机”机制和对原始数据的修改行为。多写几遍例子,你就会发现它其实非常顺手。

下一次当你需要“把一句话切成几块”,别再手动写循环了,试试 strtok(),它会让你的代码更简洁、更高效。