C语言strtok函数如何正确使用?

99ANYc3cd6
预计阅读时长 13 分钟
位置: 首页 C语言 正文

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

c 语言 strtok
(图片来源网络,侵删)

函数原型

#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;
}

输出:

c 语言 strtok
(图片来源网络,侵删)
Token: This
Token: is
Token: a
Token: test
Token: string

分析:

  1. strtok(str, ",")"This,is,a,test,string" 中找到第一个标记 "This",并在 i 后面插入 \0,然后返回指向 "This" 的指针。
  2. 循环开始,打印 "This"
  3. strtok(NULL, ",") 传入 NULLstrtok\0 的下一个位置(即 i 的位置)继续,找到下一个标记 "is",在 s 后面插入 \0,返回指向 "is" 的指针。
  4. 重复此过程,直到字符串末尾,当没有更多标记时,strtok 返回 NULL,循环结束。

注意: 在这个例子之后,原始字符串 str 已经被修改了,它变成了 "This\0is\0a\0test\0string",第一个标记 "This"str 变量本身指向同一个内存,后续的标记 "is", "a" 等是字符串中的其他部分。


重要注意事项和陷阱

strtok 虽然方便,但有几个非常重要的缺点,在现代 C 编程中经常被认为是“不安全”或“过时”的。

陷阱 1:非重入性

strtok 使用静态变量来保存状态,这意味着它不是线程安全的,如果在同一个字符串上并行调用 strtok(例如在多线程程序中),结果将是不可预测的。

c 语言 strtok
(图片来源网络,侵删)

解决方案:

  • 在单线程程序中,确保一次只在一个字符串上使用 strtok
  • 对于多线程环境,使用 strtok_r(POSIX 标准,在 Linux 和 macOS 上可用)或更现代的 C++ 方法。

陷阱 2:破坏原始字符串

如前所述,strtok 会修改原始字符串,如果你在分割后还需要使用原始字符串,或者这个字符串是 const 的,strtok 就不适用。

陷阱 3:连续的分隔符

默认情况下,strtok跳过连续的分隔符,对于字符串 "apple,,banana",使用 作为分隔符,你会得到 "apple""banana",中间的空标记会被忽略。

如果你需要处理连续分隔符产生的空标记(CSV 文件中的空字段),strtok 无法直接做到。

陷阱 4:无法处理不同的分隔符序列

strtokdelim 参数是一个字符集,而不是一个固定的分隔符字符串。delim = ",;" 表示任何逗号分号都是分隔符,它不会识别 这样的序列,如果你需要 "word1, word2;word3" 被分割成三个部分,strtok 可以做到,但如果你的分隔符是更复杂的模式(如 " -> "),strtok 就无能为力了。


现代 C++ 的替代方案

在现代 C++ 中,我们有更好的、更安全、更强大的工具来完成字符串分割,比如使用 <sstream><string>findsubstr 方法。

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++ 标准库提供的更安全、更强大的字符串处理工具。
-- 展开阅读全文 --
头像
dede栏目排序标签如何自定义排序规则?
« 上一篇 今天
织梦如何实现百度站长主动推送?
下一篇 » 今天

相关文章

取消
微信二维码
支付宝二维码

目录[+]