strtok 是 C 标准库 <string.h> 中的一个函数,它的全称是 "string token"(字符串标记),用于将一个字符串分割成一个个小的“标记”(tokens),这是处理 CSV 数据、解析配置文件或任何基于分隔符的文本时的常用函数。

函数原型
#include <string.h> char *strtok(char *str, const char *delim);
参数说明
str:- 第一次调用时: 指向你想要分割的原始字符串,函数会在这个字符串中查找第一个标记。
- 后续调用时: 必须传入
NULL,这告诉strtok继续从上次调用停止的位置开始处理同一个字符串。
delim:- 一个字符串,包含了所有你用作分隔符的字符。
- 如果你用逗号和空格来分割
"apple,banana cherry",delim应该是 。
返回值
- 成功时:返回一个指向当前找到的标记的指针(一个以
\0结尾的子字符串)。 - 当所有标记都已被找到后,返回
NULL。
工作原理(非常重要!)
strtok 的工作原理是直接修改原始字符串,它找到标记后,会在标记的末尾(即分隔符的位置)放置一个 \0 (空字符),从而将原始字符串“破坏”成多个独立的字符串。
strtok 使用静态变量来记录它在字符串中的当前位置,这就是为什么在后续调用中你必须传入 NULL,以便函数能“上次处理到哪里。
代码示例
让我们通过一个经典的例子来理解它的用法。
示例 1:基本用法
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "This,is,a,test,string";
const char *delim = ","; // 分隔符是逗号
// 第一次调用,strtok需要原始字符串
char *token = strtok(str, delim);
// 循环,直到strtok返回NULL
while (token != NULL) {
printf("Token: %s\n", token);
// 后续调用,strtok需要NULL
token = strtok(NULL, delim);
}
return 0;
}
输出:

Token: This
Token: is
Token: a
Token: test
Token: string
分析:
strtok(str, ",")在"This,is,a,test,string"中找到第一个标记"This",并在i后面插入\0,然后返回指向"This"的指针。- 循环开始,打印
"This"。 strtok(NULL, ",")传入NULL,strtok从\0的下一个位置(即i的位置)继续,找到下一个标记"is",在s后面插入\0,返回指向"is"的指针。- 重复此过程,直到字符串末尾,当没有更多标记时,
strtok返回NULL,循环结束。
注意: 在这个例子之后,原始字符串 str 已经被修改了,它变成了 "This\0is\0a\0test\0string",第一个标记 "This" 和 str 变量本身指向同一个内存,后续的标记 "is", "a" 等是字符串中的其他部分。
重要注意事项和陷阱
strtok 虽然方便,但有几个非常重要的缺点,在现代 C 编程中经常被认为是“不安全”或“过时”的。
陷阱 1:非重入性
strtok 使用静态变量来保存状态,这意味着它不是线程安全的,如果在同一个字符串上并行调用 strtok(例如在多线程程序中),结果将是不可预测的。

解决方案:
- 在单线程程序中,确保一次只在一个字符串上使用
strtok。 - 对于多线程环境,使用
strtok_r(POSIX 标准,在 Linux 和 macOS 上可用)或更现代的 C++ 方法。
陷阱 2:破坏原始字符串
如前所述,strtok 会修改原始字符串,如果你在分割后还需要使用原始字符串,或者这个字符串是 const 的,strtok 就不适用。
陷阱 3:连续的分隔符
默认情况下,strtok 会跳过连续的分隔符,对于字符串 "apple,,banana",使用 作为分隔符,你会得到 "apple" 和 "banana",中间的空标记会被忽略。
如果你需要处理连续分隔符产生的空标记(CSV 文件中的空字段),strtok 无法直接做到。
陷阱 4:无法处理不同的分隔符序列
strtok 的 delim 参数是一个字符集,而不是一个固定的分隔符字符串。delim = ",;" 表示任何逗号或分号都是分隔符,它不会识别 这样的序列,如果你需要 "word1, word2;word3" 被分割成三个部分,strtok 可以做到,但如果你的分隔符是更复杂的模式(如 " -> "),strtok 就无能为力了。
现代 C++ 的替代方案
在现代 C++ 中,我们有更好的、更安全、更强大的工具来完成字符串分割,比如使用 <sstream> 或 <string> 的 find 和 substr 方法。
C++ 示例 (使用 std::stringstream)
#include <iostream>
#include <string>
#include <sstream>
int main() {
std::string s = "This,is,a,test,string";
std::string delimiter = ",";
std::stringstream ss(s);
std::string token;
while (std::getline(ss, token, ',')) {
std::cout << "Token: " << token << std::endl;
}
return 0;
}
这个 C++ 版本同样简洁,但它是类型安全的,不会修改原始字符串 s,并且没有 strtok 的那些陷阱。
| 特性 | strtok (C) |
strtok_r (POSIX C) |
C++ stringstream / 手动分割 |
|---|---|---|---|
| 安全性 | 不安全 (非重入,修改原字符串) | 较安全 (重入) | 安全 (不修改原字符串) |
| 易用性 | 简单,但有陷阱 | 稍复杂,但更可控 | 简洁,符合 C++ 风格 |
| 功能 | 基本分割,跳过连续分隔符 | 基本分割,跳过连续分隔符 | 功能强大,可自定义逻辑 |
| 适用场景 | 快速脚本、旧代码、资源受限环境 | 多线程环境下的 C 代码 | 现代 C++ 应用程序 |
- 如果你正在写一个简单的 C 程序,并且确定没有多线程需求,也不需要保留原始字符串,
strtok是一个快速方便的选择。 - 在任何更复杂、更健壮的应用中,尤其是在 C++ 环境下,强烈建议避免使用
strtok,转而使用 C++ 标准库提供的更安全、更强大的字符串处理工具。
