首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >字符串分割函数strtok()详解

字符串分割函数strtok()详解

作者头像
byte轻骑兵
发布2026-01-20 17:38:30
发布2026-01-20 17:38:30
1690
举报

在 C 语言开发中,字符串处理是高频需求,而字符串分割(将一个完整字符串按指定分隔符拆分为多个子串)更是其中的核心场景 —— 小到命令行参数解析,大到配置文件读取、日志数据清洗,都离不开高效的分割工具。strtok()作为 C 标准库(string.h)中提供的经典分割函数,凭借简洁的接口和高效的实现,成为很多开发者的首选,但它的 “静态变量依赖”“原字符串修改” 等特性也暗藏坑点。

一、函数简介

strtok()(全称 “string tokenize”,即 “字符串令牌化”)是 C 标准库(C89 及后续标准)中定义的字符串分割函数,其核心作用是按照指定的 “分隔符集合”,将目标字符串拆分为多个连续的 “子串(令牌 token)”

核心特性速览:

  1. 依赖静态变量:首次调用时传入目标字符串,后续调用只需传入NULL,函数会通过静态变量记录上次分割的位置,继续后续拆分;
  2. 修改原字符串:分割过程中会将找到的分隔符替换为'\0'(字符串结束符),以此标记每个子串的边界,因此会破坏原字符串;
  3. 跳过连续分隔符:若遇到多个连续的分隔符(如 “a,,b” 中的两个逗号),strtok()会自动跳过,直接返回下一个非分隔符开始的子串;
  4. 线程不安全:由于使用静态变量保存分割状态,多线程同时调用strtok()分割不同字符串时,会出现状态混乱(后续会讲解决方案);
  5. 适用场景:适合简单的字符串分割需求,如命令行参数解析、简单配置项拆分(复杂场景需结合其他函数)。

头文件依赖:

使用strtok()前必须包含标准字符串头文件:

代码语言:javascript
复制
#include <string.h>

二、函数原型

strtok()的原型非常简洁,但每个参数的用法都有讲究,尤其是第一个参数的NULL传递逻辑,是初学者容易混淆的点。

完整原型:

代码语言:javascript
复制
char *strtok(char *str, const char *delim);

参数详解:

参数名

类型

含义与用法

str

char *

目标字符串: 1. 首次调用:传入需要分割的原始字符串(非const,因会被修改); 2. 后续调用:传入NULL,表示 “继续分割上一次未完成的字符串”; 3. 若传入新的非NULL字符串,会重置分割状态,开始分割新字符串。

delim

const char *

分隔符集合:一个字符串,其中每个字符都视为 “合法分隔符”(如","表示逗号是分隔符," ,;"表示空格、逗号、分号都是分隔符)。

返回值:

  • 成功:返回当前找到的 “子串(token)的首地址”;
  • 失败 / 分割结束:返回NULL(表示已遍历完整个字符串,没有更多子串可分割)。

关键逻辑示例:

假设要分割字符串"hello,world!how are you",分隔符为" ,!"(空格、逗号、感叹号),调用流程如下:

  1. 首次调用:strtok("hello,world!how are you", " ,!") → 找到第一个分隔符 “,”,替换为'\0',返回"hello"的地址,同时记录下一个位置(“world!how are you” 的首地址);
  2. 第二次调用:strtok(NULL, " ,!") → 从上次记录的位置开始,找到 “!”,替换为'\0',返回"world"的地址,记录下一个位置(“how are you” 的首地址);
  3. 第三次调用:strtok(NULL, " ,!") → 找到空格,替换为'\0',返回"how"的地址;
  4. 以此类推,直到返回NULL,分割结束。

三、工作原理与伪代码实现

要真正理解strtok(),必须搞懂其内部工作机制 —— 核心是 “静态变量保存状态” 和 “分隔符替换为'\0'”。下面通过伪代码还原其核心逻辑。

核心原理拆解:

  1. 状态保存:用静态变量last_ptr记录 “上一次分割结束后,下一个子串的起始位置”(首次调用时last_ptr为NULL);
  2. 初始化(首次调用):若str非NULL,则将last_ptr指向str的起始位置;若str为NULL,则直接从last_ptr的当前位置开始;
  3. 跳过前缀分隔符:从last_ptr开始,跳过所有属于delim的字符(处理 “开头就是分隔符” 或 “连续分隔符” 的情况);
  4. 判断是否结束:若跳过分隔符后到达字符串末尾(*last_ptr == '\0'),则返回NULL,分割结束;
  5. 标记子串边界:从当前位置开始,遍历到下一个分隔符或字符串末尾,将该分隔符(若存在)替换为'\0',标记当前子串的结束;
  6. 更新状态与返回:记录当前子串的起始地址(作为返回值),并将last_ptr指向 “被替换的'\0'的下一个位置”,供下次调用使用。

伪代码实现:

代码语言:javascript
复制
// 静态变量:保存上次分割的结束位置(跨函数调用保留状态)
static char *last_ptr = NULL;

char *strtok(char *str, const char *delim) {
    char *current_start;  // 当前子串的起始地址
    char *delim_pos;      // 找到的分隔符位置

    // 1. 初始化:首次调用传入str非NULL,重置last_ptr;后续调用用last_ptr
    if (str != NULL) {
        last_ptr = str;
    } else {
        // 若str为NULL且last_ptr已到末尾,返回NULL
        if (last_ptr == NULL || *last_ptr == '\0') {
            return NULL;
        }
    }

    // 2. 跳过当前位置的所有分隔符(处理连续分隔符/开头分隔符)
    while (*last_ptr != '\0') {
        // 检查当前字符是否在delim中
        int is_delim = 0;
        delim_pos = (char *)delim;
        while (*delim_pos != '\0') {
            if (*last_ptr == *delim_pos) {
                is_delim = 1;
                break;
            }
            delim_pos++;
        }
        if (!is_delim) {
            break;  // 找到非分隔符,停止跳过
        }
        last_ptr++;  // 是分隔符,继续向后跳
    }

    // 3. 若跳过分隔符后已到字符串末尾,返回NULL
    if (*last_ptr == '\0') {
        last_ptr = NULL;  // 重置状态,避免下次调用出错
        return NULL;
    }

    // 4. 标记当前子串的起始位置,寻找下一个分隔符
    current_start = last_ptr;
    while (*last_ptr != '\0') {
        // 检查当前字符是否是分隔符
        delim_pos = (char *)delim;
        int is_delim = 0;
        while (*delim_pos != '\0') {
            if (*last_ptr == *delim_pos) {
                is_delim = 1;
                break;
            }
            delim_pos++;
        }
        if (is_delim) {
            // 5. 找到分隔符,替换为'\0',标记子串结束
            *last_ptr = '\0';
            last_ptr++;  // 更新last_ptr到下一个位置
            return current_start;  // 返回当前子串
        }
        last_ptr++;  // 不是分隔符,继续向后找
    }

    // 6. 若遍历到字符串末尾(无更多分隔符),返回最后一个子串
    return current_start;
}

工作流程示意图:

四、使用场景:哪些场景适合用 strtok ()?

strtok()的设计定位是 “轻量级字符串分割”,因此更适合分隔符固定、无需保留原字符串、单线程的简单场景。以下是几个典型应用场景:

场景 1:命令行参数解析(模拟)

在命令行程序中,用户输入的命令(如"copy src.txt dest.txt -v")以空格为分隔符,可通过strtok()拆分命令、源文件、目标文件、选项等参数。

示例逻辑:

代码语言:javascript
复制
// 模拟命令行输入字符串
char cmd[] = "copy src.txt dest.txt -v";
// 以空格为分隔符拆分
char *token = strtok(cmd, " ");
while (token != NULL) {
    if (strcmp(token, "copy") == 0) {
        printf("命令类型:复制文件\n");
    } else if (strcmp(token, "-v") == 0) {
        printf("选项:显示详细日志\n");
    } else if (src_path == NULL) {
        src_path = token;  // 第一个非命令/选项参数:源文件路径
    } else {
        dest_path = token; // 第二个非命令/选项参数:目标文件路径
    }
    token = strtok(NULL, " ");
}
// 输出结果:
// 命令类型:复制文件
// 源文件路径:src.txt
// 目标文件路径:dest.txt
// 选项:显示详细日志

场景 2:简单配置文件解析

对于格式简单的配置文件(如key=value格式,无嵌套、无引号),可通过strtok()先按行分割,再按 “=” 分割键值对。

示例配置文件内容(config.ini):

代码语言:javascript
复制
max_conn=100
timeout=30
log_path=/var/log/app.log

解析逻辑:

代码语言:javascript
复制
FILE *fp = fopen("config.ini", "r");
if (fp == NULL) { perror("fopen"); return -1; }

char line[256];
while (fgets(line, sizeof(line), fp) != NULL) {
    // 去除行尾的换行符(fgets会读取\n)
    line[strcspn(line, "\n")] = '\0';
    // 按“=”分割key和value
    char *key = strtok(line, "=");
    char *value = strtok(NULL, "=");
    if (key != NULL && value != NULL) {
        printf("配置项:%s → %s\n", key, value);
    }
}
fclose(fp);
// 输出结果:
// 配置项:max_conn → 100
// 配置项:timeout → 30
// 配置项:log_path → /var/log/app.log

场景 3:日志数据清洗(简单分割)

对于无复杂格式的日志(如"2024-05-20 14:30:00 [INFO] user login"),可通过strtok()按空格、中括号等分隔符拆分 “时间、日志级别、内容”。

示例日志行:

代码语言:javascript
复制
2024-05-20 14:30:00 [INFO] user login

解析逻辑:

代码语言:javascript
复制
char log[] = "2024-05-20 14:30:00 [INFO] user login";
// 分隔符:空格、[、]
char *token = strtok(log, " []");
int idx = 0;
while (token != NULL) {
    switch (idx) {
        case 0: printf("日期:%s\n", token); break;
        case 1: printf("时间:%s\n", token); break;
        case 2: printf("日志级别:%s\n", token); break;
        case 3: printf("日志内容:%s ", token); break;
        default: printf("%s ", token); break;
    }
    token = strtok(NULL, " []");
    idx++;
}
// 输出结果:
// 日期:2024-05-20
// 时间:14:30:00
// 日志级别:INFO
// 日志内容:user login

场景 4:不适合的场景(避坑提醒)

strtok()并非万能,以下场景需避免使用:

  1. 需要保留原字符串:因strtok()会修改原字符串,若后续需使用原始内容,需先复制字符串(如用strcpy());
  2. 多线程环境:静态变量导致线程不安全,需用strtok_r()(可重入版)替代;
  3. 复杂格式分割:如 CSV 文件(含引号包裹的分隔符,如"a,b",c),strtok()无法处理,需自定义逻辑或使用专门的 CSV 解析库;
  4. 宽字符字符串(wchar_t):strtok()仅支持char类型,宽字符需用wcsrtok()。

五、注意事项:避坑指南

strtok()的特性决定了它有很多 “隐性坑”,稍有不慎就会导致程序崩溃或逻辑错误。以下是必须掌握的 6 个注意事项,每个都搭配示例说明:

注意 1:原字符串会被修改,且不能是 const 类型

strtok()会将分隔符替换为'\0',因此必须传入非 const 的可修改字符串(若传入const char *,编译器会报错,运行时可能崩溃);同时,若后续需要原字符串,必须先复制。

错误示例(const 字符串):

代码语言:javascript
复制
// 错误:str是const类型,不可修改
const char str[] = "hello,world";
char *token = strtok(str, ",");  // 编译器报错:传递参数 1 时将丢弃指针目标类型的限定符

正确示例(复制原字符串):

代码语言:javascript
复制
const char original[] = "hello,world";
// 先复制原字符串到可修改的缓冲区
char str[256];
strcpy(str, original);
// 再分割
char *token = strtok(str, ",");
printf("子串1:%s\n", token);       // 输出:hello
printf("原字符串(original):%s\n", original);  // 输出:hello,world(未修改)
printf("复制后字符串(str):%s\n", str);        // 输出:hello(因第二个字符被改为'\0')

注意 2:线程不安全,多线程需用 strtok_r ()

strtok()使用静态变量last_ptr保存状态,若多个线程同时调用strtok()分割不同字符串,会导致last_ptr被交叉修改,出现 “分割混乱”。

线程不安全示例(简化):

代码语言:javascript
复制
#include <pthread.h>
#include <stdio.h>
#include <string.h>

// 线程1:分割字符串"a,b,c"
void *thread1(void *arg) {
    char str[] = "a,b,c";
    char *token = strtok(str, ",");
    while (token != NULL) {
        printf("线程1:%s\n", token);
        token = strtok(NULL, ",");
        // 模拟耗时操作,让线程2有机会介入
        sleep(1);
    }
    return NULL;
}

// 线程2:分割字符串"x;y;z"
void *thread2(void *arg) {
    char str[] = "x;y;z";
    char *token = strtok(str, ";");
    while (token != NULL) {
        printf("线程2:%s\n", token);
        token = strtok(NULL, ";");
        sleep(1);
    }
    return NULL;
}

int main() {
    pthread_t t1, t2;
    pthread_create(&t1, NULL, thread1, NULL);
    pthread_create(&t2, NULL, thread2, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    return 0;
}

预期输出:线程 1 输出 a→b→c,线程 2 输出 x→y→z;

实际输出(混乱):

线程1:a 线程2:x 线程1:y // 错误:线程1读取了线程2的last_ptr状态 线程2:z 线程1:NULL // 提前结束

解决方案:用strtok_r()(r表示 “reentrant,可重入”),它通过用户传入的指针保存状态,而非静态变量,线程安全。

注意 3:连续分隔符会被跳过,需确认业务是否允许

strtok()会自动跳过连续的分隔符(如 “a,,b” 分割后得到 “a” 和 “b”,中间的空串被丢弃)。若业务需要保留空串(如 CSV 文件中 “a,,b” 表示三个字段:a、空、b),则strtok()不适用,需用strsep()或自定义逻辑。

示例:

代码语言:javascript
复制
char str[] = "a,,b;c;;d";
char *token = strtok(str, ",;");
while (token != NULL) {
    printf("子串:%s\n", token);
    token = strtok(NULL, ",;");
}
// 输出结果(跳过空串):
// 子串:a
// 子串:b
// 子串:c
// 子串:d

注意 4:首次调用必须传入非 NULL,后续需传 NULL

  • 若首次调用传入NULL,last_ptr初始为NULL,函数会直接返回NULL,分割失败;
  • 若分割同一字符串时,中途传入新的非NULL字符串,会重置last_ptr,原字符串的分割会中断,开始分割新字符串。

错误示例(首次传 NULL):

代码语言:javascript
复制
// 错误:首次调用传入NULL,直接返回NULL
char *token = strtok(NULL, ",");
if (token == NULL) {
    printf("分割失败:首次调用必须传入非NULL字符串\n");
}

中途切换字符串示例:

代码语言:javascript
复制
char str1[] = "a,b,c";
char str2[] = "x,y,z";

// 首次调用:分割str1
char *token = strtok(str1, ",");
printf("str1子串:%s\n", token);  // 输出:a

// 中途传入str2(非NULL),重置状态,开始分割str2
token = strtok(str2, ",");
printf("str2子串:%s\n", token);  // 输出:x

// 后续传NULL,继续分割str2(而非str1)
token = strtok(NULL, ",");
printf("str2子串:%s\n", token);  // 输出:y

// str1的分割已中断,无法继续获取b、c

注意 5:分隔符是 “集合”,而非 “固定字符串”

delim参数是 “分隔符字符集合”,而非 “分隔符字符串”—— 例如delim=" ,!"表示 “空格、逗号、感叹号中的任意一个字符都是分隔符”,而非 “空格 + 逗号 + 感叹号” 的组合。

示例:

代码语言:javascript
复制
// 分隔符:空格、逗号、感叹号(任意一个都生效)
char str[] = "hello world,test!demo";
char *token = strtok(str, " ,!");
while (token != NULL) {
    printf("子串:%s\n", token);
    token = strtok(NULL, " ,!");
}
// 输出结果:
// 子串:hello
// 子串:world
// 子串:test
// 子串:demo

注意 6:分割结束后,建议重置 last_ptr(可选)

strtok()的静态变量last_ptr会一直保留状态,若后续再次调用strtok()分割新字符串,需确保首次调用传入非NULL(会自动重置last_ptr);若担心状态残留,可手动将last_ptr置NULL(需注意:静态变量在函数外部不可直接访问,可通过 “传入空字符串 + 任意分隔符” 间接重置)。

重置示例:

代码语言:javascript
复制
// 间接重置last_ptr:传入空字符串,函数会将last_ptr置NULL
strtok("", ",");

六、完整示例代码:从基础到进阶

以下提供 3 个递进式的示例代码,覆盖strtok()的基础用法、线程安全替代方案(strtok_r())、与其他分割函数的对比,可直接编译运行(GCC 环境:gcc -o strtok_demo strtok_demo.c -lpthread)。

示例 1:基础用法 —— 分割带多种分隔符的字符串

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>

int main() {
    // 原字符串:带空格、逗号、感叹号、分号
    char str[] = "I love C programming, it's fun! Let's learn; together";
    // 分隔符集合:空格、, ! ; '(注意单引号也是分隔符)
    const char *delim = " ,!;'";

    printf("原始字符串:%s\n", str);
    printf("分割结果:\n");

    // 首次调用:传入str和delim
    char *token = strtok(str, delim);
    int count = 0;  // 统计子串个数

    while (token != NULL) {
        count++;
        printf("子串%d:%s\n", count, token);
        // 后续调用:传入NULL
        token = strtok(NULL, delim);
    }

    printf("分割完成,共得到%d个子串\n", count);
    printf("修改后的原字符串:%s\n", str);  // 原字符串已被修改(第一个分隔符后为'\0')

    return 0;
}

运行结果

示例 2:线程安全 —— 用 strtok_r () 替代 strtok ()

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

// 线程1:用strtok_r()分割"a,b,c,d"
void *thread1(void *arg) {
    char str[] = "a,b,c,d";
    const char *delim = ",";
    char *save_ptr;  // 用于保存分割状态(替代strtok()的静态变量)

    printf("线程1开始分割:%s\n", str);
    // 首次调用strtok_r:传入str、delim、&save_ptr
    char *token = strtok_r(str, delim, &save_ptr);
    while (token != NULL) {
        printf("线程1:%s\n", token);
        sleep(1);  // 模拟耗时操作
        // 后续调用:传入NULL、delim、&save_ptr
        token = strtok_r(NULL, delim, &save_ptr);
    }
    printf("线程1分割结束\n");
    return NULL;
}

// 线程2:用strtok_r()分割"x;y;z;w"
void *thread2(void *arg) {
    char str[] = "x;y;z;w";
    const char *delim = ";";
    char *save_ptr;

    printf("线程2开始分割:%s\n", str);
    char *token = strtok_r(str, delim, &save_ptr);
    while (token != NULL) {
        printf("线程2:%s\n", token);
        sleep(1);
        token = strtok_r(NULL, delim, &save_ptr);
    }
    printf("线程2分割结束\n");
    return NULL;
}

int main() {
    pthread_t t1, t2;
    // 创建线程
    pthread_create(&t1, NULL, thread1, NULL);
    pthread_create(&t2, NULL, thread2, NULL);
    // 等待线程结束
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    return 0;
}

运行结果(线程安全,无混乱):

示例 3:对比 strtok ()、strtok_r ()、strsep ()

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>

// 对比三个分割函数:处理连续分隔符的差异
int main() {
    // 测试字符串:含连续分隔符
    char str1[] = "a,,b;c;;d";
    char str2[] = "a,,b;c;;d";
    char str3[] = "a,,b;c;;d";
    const char *delim = ",;";

    printf("=== 1. strtok() 分割(跳过连续分隔符) ===\n");
    char *t1 = strtok(str1, delim);
    while (t1 != NULL) {
        printf("%s ", t1);
        t1 = strtok(NULL, delim);
    }
    printf("\n");

    printf("=== 2. strtok_r() 分割(跳过连续分隔符,线程安全) ===\n");
    char *save_ptr;
    char *t2 = strtok_r(str2, delim, &save_ptr);
    while (t2 != NULL) {
        printf("%s ", t2);
        t2 = strtok_r(NULL, delim, &save_ptr);
    }
    printf("\n");

    printf("=== 3. strsep() 分割(保留连续分隔符的空串) ===\n");
    char *t3 = str3;
    while ((t3 = strsep(&t3, delim)) != NULL) {
        // 空串输出"<空>"
        if (*t3 == '\0') {
            printf("<空> ");
        } else {
            printf("%s ", t3);
        }
    }
    printf("\n");

    return 0;
}

运行结果(关键差异:连续分隔符的处理):

代码语言:javascript
复制
=== 1. strtok() 分割(跳过连续分隔符) ===
a b c d 
=== 2. strtok_r() 分割(跳过连续分隔符,线程安全) ===
a b c d 
=== 3. strsep() 分割(保留连续分隔符的空串) ===
a <空> b c <空> d 

注意:strsep()是 BSD 扩展函数(非 C 标准),在 GCC、Clang 中可用,但在 VC 等编译器中可能不支持,移植性较差。

七、差异对比:strtok () vs strtok_r () vs strsep ()

实际开发中,除了strtok(),strtok_r()和strsep()也是常用的分割函数。下表从核心维度对比三者的差异,帮你快速选择合适的工具:

对比维度

strtok()

strtok_r ()(C99 标准)

strsep ()(BSD 扩展)

线程安全性

不安全(静态变量)

安全(用户提供 save_ptr)

安全(无静态变量)

原字符串修改

是(分隔符替换为 '\0')

连续分隔符处理

跳过(丢弃空串)

跳过(丢弃空串)

保留(返回空串)

状态保存方式

静态变量(内部维护)

用户传入的 save_ptr(外部维护)

传入的字符串指针(外部维护)

函数原型

char* strtok(char*, const char*)

char* strtok_r(char*, const char*, char**)

char* strsep(char**, const char*)

兼容性

所有 C 编译器支持(C89+)

主流编译器支持(C99+)

BSD、Linux 支持,Windows 不支持

适用场景

单线程、简单分割、无需保留空串

多线程、简单分割、无需保留空串

单 / 多线程、需保留空串(如 CSV)

核心差异总结:

  1. 线程安全:优先选strtok_r()(标准)或strsep()(非标准),避免strtok();
  2. 空串保留:需保留连续分隔符之间的空串(如 CSV),选strsep();无需则选strtok_r();
  3. 兼容性:跨平台(尤其是 Windows)开发,选strtok_r();仅 Linux/BSD 环境,可按需选strsep()。

strtok()作为 C 语言中的经典字符串分割函数,凭借简洁的接口和高效的实现,在单线程、简单分割场景中仍有广泛应用。但它的 “静态变量依赖”“原字符串修改”“跳过空串” 等特性,也要求开发者必须深入理解其原理,才能避免踩坑。

核心要点回顾

  1. 首次调用传目标字符串,后续传NULL,依赖静态变量保存状态;
  2. 分割过程会修改原字符串(分隔符替换为'\0'),需保留原字符串时先复制;
  3. 多线程环境必须用strtok_r()替代,需保留空串用strsep();
  4. 分隔符是 “字符集合”,连续分隔符会被跳过,需确认业务是否允许。

掌握strtok()的同时,也建议了解strtok_r()、strsep()等替代方案,根据实际场景(线程、空串需求、兼容性)选择最合适的工具,才能写出更健壮、更易维护的代码。


经典面试题

问:strtok () 为什么是线程不安全的?如何解决这个问题?

strtok()线程不安全的核心原因是它使用静态变量保存上次分割的位置(last_ptr)—— 静态变量属于进程全局资源,多线程同时调用strtok()时,会交叉修改last_ptr,导致分割状态混乱(如线程 A 的分割被线程 B 的调用打断,后续线程 A 会读取线程 B 的last_ptr)。

解决方法

  1. 改用 C99 标准中的 **strtok_r()**(可重入版):它通过用户传入的char **save_ptr参数保存分割状态(而非静态变量),每个线程可独立维护自己的save_ptr,实现线程安全;
  2. 单线程化处理:在多线程中通过互斥锁(如pthread_mutex_t)保护strtok()的调用,确保同一时间只有一个线程使用strtok(),但会降低效率,不推荐;
  3. 改用非静态变量维护状态的函数(如 BSD 扩展的strsep()),但需注意兼容性。

问:使用 strtok () 分割字符串时,原字符串会被修改吗?为什么?如果需要保留原字符串,该怎么做?

:会修改原字符串。

原因:strtok()的分割逻辑是 “找到分隔符后,将其替换为'\0'(字符串结束符)”,以此标记当前子串的边界,同时通过静态变量记录下一个子串的起始位置 —— 这个替换操作直接修改了原字符串的内存内容,因此原字符串会被破坏。

保留原字符串的解决方案:

在调用strtok()前,先将原字符串复制到一个可修改的缓冲区(如用strcpy()、memcpy()),再对缓冲区调用strtok()进行分割。示例代码:

代码语言:javascript
复制
const char original[] = "hello,world";  // 原字符串(不可修改)
char buf[256];
strcpy(buf, original);  // 复制到可修改的缓冲区
char *token = strtok(buf, ",");  // 对缓冲区分割,不影响original

问:对比 strtok () 和 strsep (),它们在处理连续分隔符时有什么不同?各自适合什么场景?

两者在处理连续分隔符时的核心差异是是否保留空串

  1. strtok():会自动跳过连续的分隔符,不返回空串。例如分割 “a,,b”(分隔符为 “,”),会返回 “a” 和 “b”,中间的空串被丢弃;
  2. strsep():不会跳过连续的分隔符,会返回空串。例如分割 “a,,b”,会返回 “a”、空串("")、“b”,保留连续分隔符之间的空串。

适用场景:

  • strtok():适合无需保留空串的简单分割场景,如命令行参数解析(空格分隔,连续空格无意义)、配置文件key=value拆分(无连续分隔符);
  • strsep():适合需要保留空串的场景,如 CSV 文件解析(“a,,b” 表示三个字段:a、空、b)、日志格式中固定字段的分割(即使字段为空也需保留位置)。

注意:strsep()是 BSD 扩展函数,非 C 标准,移植性(如 Windows)不如strtok()的标准替代strtok_r()。


博主简介 byte轻骑兵,现就职于国内知名科技企业,专注于嵌入式系统研发,深耕 Android、Linux、RTOS、通信协议、AIoT、物联网及 C/C++ 等领域。乐于技术分享与交流,欢迎关注互动! 📌 主页与联系方式

  • CSDN:https://blog.csdn.net/weixin_37800531
  • 知乎:https://www.zhihu.com/people/38-72-36-20-51
  • 微信公众号:嵌入式硬核研究所
  • 邮箱:byteqqb@163.com(技术咨询或合作请备注需求)

⚠️ 版权声明 本文为原创内容,未经授权禁止转载。商业合作或内容授权请联系邮箱并备注来意。


本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-10-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、函数简介
  • 二、函数原型
  • 三、工作原理与伪代码实现
  • 四、使用场景:哪些场景适合用 strtok ()?
  • 五、注意事项:避坑指南
  • 六、完整示例代码:从基础到进阶
  • 七、差异对比:strtok () vs strtok_r () vs strsep ()
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档