函数简介
strtok 是 C 标准库 <string.h> 中的一个函数,它的全称是 "string token"(字符串标记),它的主要作用是:将一个字符串按照指定的分隔符(一个或多个字符)进行分割,并逐个返回分割后的子串(标记)。

函数原型
#include <string.h> char *strtok(char *str, const char *delim);
参数说明
-
char *str:- 首次调用时:指向你想要分割的源字符串,函数会在这个字符串中查找标记,并对其进行修改(非常重要!)。
- 后续调用时:必须传入
NULL,这告诉strtok继续从上一次分割的位置开始处理同一个字符串。
-
const char *delim:- 这是一个分隔符字符串,它包含了所有可以作为分隔符的字符。
- 表示空格、逗号和句号都可以作为分隔符。
strtok会将 一个或多个连续 的分隔符视为一个分隔符。
返回值
- 成功时:返回一个指向当前找到的标记(子串)的
char*指针。 - 当字符串中所有的标记都被找到后,再次调用会返回
NULL。 str和delim都是NULL,或者str是空字符串 ,行为是未定义的。
工作原理(非常重要!)
理解 strtok 的工作原理是正确使用它的关键,它依赖于一个静态的、内部的指针来记录其状态。
-
首次调用:
(图片来源网络,侵删)strtok接收源字符串str。- 它会跳过开头的所有分隔符。
- 然后找到标记的起始位置,并开始向后扫描,直到遇到一个分隔符或字符串的结尾
\0。 - 关键操作:在遇到分隔符的地方,它会用
\0(字符串结束符) 来覆盖它,从而将源字符串“分割”成多个独立的字符串。 - 它返回指向这个新创建的子串的指针。
- 它会内部记录下下一个应该开始查找的位置(即
\0后面的那个字符)。
-
后续调用:
- 当你传入
NULL作为第一个参数时,strtok知道这不是一个新字符串,而是要继续上一次的工作。 - 它会从内部记录的位置开始继续扫描。
- 同样,它会跳过所有连续的分隔符,找到下一个标记,用
\0截断,并返回指针。 - 这个过程会一直持续,直到字符串的末尾,此时它会返回
NULL。
- 当你传入
代码示例
下面的例子清晰地展示了如何使用 strtok 来分割一个由逗号和空格组成的字符串。
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "apple,banana orange,grape";
const char *delim = ", "; // 分隔符是逗号或空格
// 首次调用,传入源字符串
char *token = strtok(str, delim);
// 循环获取所有标记,直到 strtok 返回 NULL
while (token != NULL) {
printf("Token: %s\n", token);
// 后续调用,传入 NULL
token = strtok(NULL, delim);
}
return 0;
}
输出结果:
Token: apple
Token: banana
Token: orange
Token: grape
关键点与注意事项
1 原地修改字符串
strtok 会直接修改源字符串,在上面的例子中,执行完 strtok 后,str 数组的内容已经发生了改变,它实际上变成了这样:
"apple\0banana\0orange\0grape\0"
这意味着:
- 不能将
strtok应用于字符串字面量(常量字符串),因为尝试修改常量会导致未定义行为(通常是程序崩溃)。// 错误示例! char *str = "hello, world"; // str 指向只读内存区 strtok(str, ", "); // 尝试修改只读内存,崩溃!
- 如果你需要保留原始字符串,必须先创建一个副本,然后对副本进行操作。
2 线程安全问题
strtok 使用一个静态的内部指针来记录状态,这意味着strtok 不是线程安全的,如果在多线程环境中同时调用 strtok 来分割不同的字符串,它们会互相干扰,导致不可预期的结果。
在多线程环境下,应该使用 strtok_r(POSIX 标准,Linux/Unix/macOS 支持)或 strtok_s(C11 标准,Windows 支持),这两个函数是 strtok 的线程安全版本,它们通过一个额外的参数让调用者自己管理状态指针。
3 连续的分隔符
strtok 会将一个或多个连续的分隔符视为一个分隔符,对于字符串 "a,,b",使用 作为分隔符,会得到 "a" 和 "b",中间不会出现空的标记。
4 无法识别空字符串作为标记
由于 strtok 会跳过开头的所有分隔符,它无法识别开头或结尾的空标记,对于字符串 ",a,b,",使用 作为分隔符,只会得到 "a" 和 "b",开头的和结尾的空标记会被忽略。
strtok 的现代替代方案
由于 strtok 有诸多限制(非线程安全、修改源字符串、无法处理空标记等),在现代C++或需要更健壮代码的C项目中,通常会使用更安全、更灵活的替代方案。
1 C++ 的 std::stringstream
这是在C++中最推荐、最安全、最优雅的方式。
#include <iostream>
#include <string>
#include <sstream>
int main() {
std::string text = " apple, banana ,orange, ";
std::stringstream ss(text);
std::string token;
char delimiter = ','; // 可以是任意字符
while (std::getline(ss, token, delimiter)) {
// std::getline 不会自动去除空格,可以手动处理
size_t start = token.find_first_not_of(" ");
if (start != std::string::npos) {
token = token.substr(start);
std::cout << "Token: " << token << std::endl;
}
}
return 0;
}
2 C++ 的 std::string::find
手动使用 std::string 的 find 和 substr 方法可以实现完全自定义的分割逻辑,更灵活。
3 C11 的 strtok_s
如果你必须在C语言环境中工作,并且编译器支持C11标准,strtok_s 是比 strtok 更好的选择。
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "apple,banana orange,grape";
const char *delim = ", ";
char *context = NULL; // 用于保存状态的上下文指针
char *token;
// 首次调用,context 传入 NULL
token = strtok_s(str, delim, &context);
while (token != NULL) {
printf("Token: %s\n", token);
// 后续调用,传入 context
token = strtok_s(NULL, delim, &context);
}
return 0;
}
| 特性 | strtok |
strtok_r / strtok_s |
std::stringstream (C++) |
|---|---|---|---|
| 线程安全 | 否 | 是 | 是 |
| 修改源字符串 | 是 | 是 | 否 |
| 处理空标记 | 不能 | 不能 | 可以(通过逻辑实现) |
| 易用性 | 简单 | 中等 | 非常简单 |
| 适用标准 | C89/C90 | POSIX (C) / C11 (Windows) | C++ |
| 推荐场景 | 快速脚本、非关键代码、旧项目兼容 | 多线程C代码 | 现代C++项目 |
核心建议:
- 在C++中,永远优先使用
std::stringstream或其他STL工具。 - 在C语言中,如果可能,尽量使用
strtok_s(Windows)或strtok_r(Linux/Unix)。 - 仅在单线程、临时性、不介意修改源字符串的简单场景下,才考虑使用
strtok。
