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(),它会让你的代码更简洁、更高效。