C中strtok()函数和strtok_r()函数

本文介绍了C语言中的strtok和strtok_r函数,重点讨论了strtok函数在处理字符串分隔时的问题以及如何通过strtok_r函数来解决这个问题。通过示例展示了strtok_r函数在连续切分字符串时如何维护指针状态,提高了代码的灵活性和安全性。

1.

函数名:strtok

功能:查找由第二个串指定的分界符分开的单词

用法:char   *strtok(char   *str1,   char   *str2)

一个例子:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
        char input[16] = "abc,d";
        char *p; 
        p = strtok(input, ",");
        if(p) 
                printf("%s\n", p); 
        p = strtok(NULL, ",");
        if(p)
                printf("%s\n", p); 
        return 0;
}
第一次执行要以input为第一参数,第二次执行要以NULL为第一参数.

执行结果为

abc

d

第二个例子:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
        int in = 0;
        char buffer[20] = "Fred,John,Ann";
        char *p[3];
        char *buf = buffer;
        while((p[in] = strtok(buf, ",")) != NULL)
        {   
                in++;
                buf = NULL;
        }   
        printf("buffer = %s\n", buffer);
        printf("p1 = %s\n", p[0]);
        printf("p2 = %s\n", p[1]);
        printf("p3 = %s\n", p[2]);
}
执行结果为:
buffer = Fred
p1 = Fred
p2 = John
p3 = Ann

2.strtok()函数的弱点

如果一段字符串为"Fred male 25,John male 62,Anna female 16"

我们希望把这个字符串整理输出到一个struct

struct person

{

char name[25];

char sex[10];

char age[5];

}

我们首先想把上面的字符串按","分隔,然后再将第一次分隔后的字符串按" "(空个)分隔.

想法程序如下:

#include <stdio.h>
#include <string.h>

int main()
{
        int in = 0, j;
        char buffer[100] = "Fred male 25,John male 62,Anna female 16";
        char *p[20];
        char *buf = buffer;
        while((p[in] = strtok(buf, ",")) != NULL)
        {   
                buf = p[in];
                while((p[in] = strtok(buf, " ")) != NULL)
                {   
                        in++;
                        buf = NULL;
                }   
                p[in++] = "****";  //to present the symbol....
                buf = NULL;
        }   
        printf("Here we have %d strings \n", in);
        for(j = 0; j < in; j++)
                printf(">%s<\n", p[j]);
        return 0;
}
执行结果为:

>Fred<
>male<
>25<
>****<

显然没有得到我们想要的结果.

原因是:在第一次外循环中,strtok将"Fred male 25,"后的这个逗号,改为了'/0’,这时strtok内部的this指针指向的是逗号的后一个字符'J’经过第一次的内循环,分别提取出了“Fred” “male” “25”。提取完"25”之后,函数内部的this指针被修改指向了"25”后面的'/0’内循环结束后(内循环实际执行了4次),开始第二次的外循环,由于函数第一个参数被设定为NULL,strtok将以this指针指向的位置作为分解起始位置。很遗憾,此时this指针指向的是'/0’,strtok对一个空串无法切分,返回NULL。外循环结束。所以,我们只得到了如图所示的第一个人的信息。

2.上面问題的解决办法:使用strtok_r()函数.

函数原型:char *strtok_r(char *s, const char *delim, char **ptrptr);

strtok_r实际上就是将strtok内部隐式保存的this指针,以参数的形式与函数外部进行交互。由调用者进行传递、保存甚至是修改。需要调用者在连续切分相同源字符串时,除了将str参数赋值为NULL,还要传递上次切分时保存下的saveptr。

程序如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main()
{
        int in = 0, j;
        char buffer[100] = "Fred male 25,John male 62,Anna female 16";
        char *p[20];
        char *buf = buffer;
        char *outer_ptr = NULL;
        char *inner_ptr = NULL;
        while((p[in] = strtok_r(buf, ",", &outer_ptr)) != NULL) 
        {   
                buf = p[in];
                while((p[in] = strtok_r(buf, " ", &inner_ptr)) != NULL)
                {    
                        in++;
                        buf = NULL;
                }   
                buf = NULL;
        }   
        printf("Here we have %d strings\n", in);
        for(j = 0; j < in; j++)
                printf(">%s<\n", p[j]);
        return 0;
}
执行结果:

>Fred<
>male<
>25<
>John<
>male<
>62<
>Anna<
>female<
>16<

调用strtok_r的代码比调用strtok的代码多了两个指针,outer_ptr和inner_ptr。outer_ptr用于标记每个人的提取位置,即外循环;inner_ptr用于标记每个人内部每项信息的提取位置,即内循环。具体过程如下:

(1)第1次外循环,outer_ptr忽略,对整个源串提取,提取出"Fred male 25",分隔符',' 被修改为了'/0’,outer_ptr返回指向'J’。

(2)第一次内循环,inner_ptr忽略对第1次外循环的提取结果"Fred male 25"进行提取,提取出了"Fred",分隔符' '被修改为了'/0',inner_ptr返回指向'm'。

(3)第二次内循环,传递第一次内循环返回的inner_ptr,第一个参数为NULL,从inner_ptr指向的位置'm'开始提取,提取出了"male",分隔符  ' '被修改为了'/0',inner_ptr返回指向'2'。

(4)第三次内循环,传递第二次内循环返回的inner_ptr,第一个参数为NULL,从inner_ptr指向的位置'2'开始提取,提取出了"25",因为没有找到' ',inner_ptr返回指向25后的'/0'。

(5)第四次内循环,传递第三次内循环返回的inner_ptr,第一个参数为NULL,因为inner_ptr指向的位置为'/0',无法提取,返回空值。结束内循环。

(6)第2次外循环,传递第1次外循环返回的outer_ptr,第一个参数为NULL,从outer_ptr指向的位置'J'开始提取,提取出"John male 62",分隔符',’被修改为了'/0’,outer_ptr返回指向'A’。(调用strtok则卡死在了这一步

……以此类推,外循环一次提取一个人的全部信息,内循环从外循环的提取结果中,二次提取个人单项信息。

可以看到strtok_r将原内部指针显示化,提供了saveptr这个参数。增加了函数的灵活性和安全性。



<think>我们正在讨论C语言中的strtok函数。根据用户提供的引用资料,我们可以总结出strtok函数的功能用法。以下是一个结构化的回答: ### C语言 `strtok` 函数详解 #### 一、功能说明 `strtok` 是C语言标准库(`<string.h>`)中的字符串分割函数,主要用于: 1. **将字符串按指定分隔符拆分成多个子字符串(token)** 2. **可连续调用以处理同一字符串的剩余部分** 3. **修改原始字符串(用`\0`替换分隔符)**,因此操作的对象应是可修改的字符串(例如字符数组,而非字符串常量) 函数原型: ```c char *strtok(char *str, const char *delim); ``` #### 二、参数解析 | 参数 | 说明 | |-----------|----------------------------------------------------------------------| | `str` | 待分割的字符串(首次调用时传入;后续调用传入`NULL`以继续分割) | | `delim` | 分隔符组成的字符串(如`","`或`" \t\n"`,每个字符都视为独立分隔符) | #### 三、返回值 - 成功:返回指向当前分割出的子字符串的指针 - 失败/无更多token:返回`NULL` --- #### 四、使用示例 ##### 示例1:基础分割(逗号分隔) ```c #include <stdio.h> #include <string.h> int main() { char str[] = "apple,banana,cherry"; // 必须是可修改的数组 char *token = strtok(str, ","); // 首次调用传入字符串 while (token != NULL) { printf("%s\n", token); // 输出当前token token = strtok(NULL, ","); // 后续调用传入NULL } return 0; } ``` **输出**: ``` apple banana cherry ``` ##### 示例2:多分隔符(空格分号) ```c #include <stdio.h> #include <string.h> int main() { char str[] = "cat; dog elephant"; char *token = strtok(str, " ;"); // 分隔符为空格分号 while (token != NULL) { printf("%s\n", token); token = strtok(NULL, " ;"); } return 0; } ``` **输出**: ``` cat dog elephant ``` ##### 示例3:安全处理用户输入(结合`fgets`) ```c #include <stdio.h> #include <string.h> int main() { char input[100]; printf("输入数据(用逗号分隔): "); fgets(input, sizeof(input), stdin); // 安全读取输入 input[strcspn(input, "\n")] = '\0'; // 去除换行符 char *token = strtok(input, ","); while (token != NULL) { printf("Token: %s\n", token); token = strtok(NULL, ","); } return 0; } ``` --- #### 五、关键注意事项 1. **修改原始字符串**: `strtok` 会在分隔符位置插入 `\0`,因此: - 不可对字符串常量使用(如 `char *s = "test";` 会导致未定义行为) - 应使用字符数组(如 `char s[] = "test";`) 2. **线程不安全**: `strtok` 使用静态缓冲区保存位置,多线程环境下需用 `strtok_r`(POSIX标准)[^3]。 3. **连续分隔符处理**: 若存在连续分隔符(如 `"a,,b"`),`strtok` 会跳过空字段,不返回空字符串[^4]。 4. **重置分割状态**: 若要重新分割同一字符串,需再次用非`NULL`的`str`参数调用`strtok`[^2]。 --- #### 六、内部实现原理(简化版) 参考引用[5]中的实现思路,`strtok` 的核心逻辑如下: 1. 若`str != NULL`,从`str`起始位置开始扫描;若`str == NULL`,则从上一次保存的位置继续。 2. 跳过起始的分隔符(连续分隔符场景)。 3. 记录当前token的起始地址,扫描直到遇到分隔符或字符串结束。 4. 将当前遇到的分隔符替换为`\0`,并保存下一个字符的地址(供后续调用使用)。 5. 返回当前token的起始地址。 > 注:标准库的具体实现可能更复杂,但基本原理一致。 --- §§ 相关问题 §§ 1. `strtok` `strtok_r` 有什么区别? 2. 如何用 `strtok` 处理包含空字段的CSV数据(如 `"a,,b"`)? 3. 除了 `strtok`,C语言还有哪些安全的字符串分割方法? 4. 为什么 `strtok` 在多线程环境下不安全? [^1]: C语言 strtok字符串分割函数详解 [^2]: C语言strtok()函数用法详解! [^3]: strtok函数详解 看这一篇就够了-C语言 [^4]: strtok函数的使用 [^5]: c语言strtok函数完美实现</think>### C语言 `strtok` 函数详解 #### 一、功能说明 `strtok` 是C语言标准库(`<string.h>`)中的字符串分割函数,主要功能: 1. **按指定分隔符拆分字符串**:将字符串分割成多个子字符串(token) 2. **修改原始字符串**:将分隔符替换为 `\0`(因此操作对象必须是可修改的字符数组) 3. **支持连续调用**:首次调用传入源字符串,后续调用传入 `NULL` 继续分割 4. **跳过连续分隔符**:自动忽略连续出现的分隔符(不返回空字符串) 函数原型: ```c char *strtok(char *str, const char *delim); ``` #### 二、参数解析 | 参数 | 说明 | |-----------|----------------------------------------------------------------------| | `str` | 待分割的字符串(首次调用时传入;后续调用传入`NULL`以继续分割) | | `delim` | 分隔符组成的字符串(如`","`或`" \t\n"`,每个字符都视为独立分隔符) | #### 三、返回值 - 成功:返回当前分割出的子字符串指针 - 失败/无更多token:返回 `NULL` --- #### 四、使用示例 ##### 示例1:基础分割(逗号分隔) ```c #include <stdio.h> #include <string.h> int main() { char str[] = "apple,banana,cherry"; // 必须是可修改的数组 char *token = strtok(str, ","); // 首次调用 while (token != NULL) { printf("%s\n", token); // 输出当前token token = strtok(NULL, ","); // 后续调用 } return 0; } ``` **输出**: ``` apple banana cherry ``` ##### 示例2:多分隔符(空格分号) ```c #include <stdio.h> #include <string.h> int main() { char str[] = "cat; dog elephant"; char *token = strtok(str, " ;"); // 分隔符为空格分号 while (token != NULL) { printf("%s\n", token); token = strtok(NULL, " ;"); } return 0; } ``` **输出**: ``` cat dog elephant ``` ##### 示例3:解析CSV数据(含空字段) ```c #include <stdio.h> #include <string.h> int main() { char data[] = "John,Doe,25,London"; char *fields[4]; int i = 0; char *token = strtok(data, ","); while (token != NULL && i < 4) { fields[i++] = token; // 存储每个字段指针 token = strtok(NULL, ","); } printf("Name: %s %s\nAge: %s\nCity: %s\n", fields[0], fields[1], fields[2], fields[3]); return 0; } ``` --- #### 五、关键注意事项 1. **修改原始字符串**: `strtok` 会在分隔符位置插入 `\0`,因此: - 不可对字符串常量使用(如 `char *s = "test";` 会导致未定义行为) - 应使用字符数组(如 `char s[] = "test";`) 2. **线程不安全**: `strtok` 使用静态缓冲区保存位置,多线程环境下需用 `strtok_r`(POSIX标准)[^3]。 3. **连续分隔符处理**: 若存在连续分隔符(如 `"a,,b"`),`strtok` 会跳过空字段,不返回空字符串[^4]。 4. **重置分割**: 若要重新分割同一字符串,需再次用非`NULL`的`str`参数调用`strtok`[^2]。 --- #### 六、内部实现原理 参考引用[5],`strtok` 的核心操作: 1. 首次调用时扫描字符串,找到第一个非分隔符字符作为token起始 2. 将遇到的第一个分隔符替换为 `\0` 3. 记录下一个token的起始位置(静态变量保存) 4. 后续调用从记录位置继续扫描 5. 当扫描到字符串结尾时返回 `NULL` > 示例实现伪代码: > ```c > static char *save_ptr; // 保存位置 > > char *strtok(char *str, const char *delim) { > if (str) save_ptr = str; // 初始化位置 > if (!save_ptr) return NULL; > > // 跳过起始分隔符 > while (*save_ptr && strchr(delim, *save_ptr)) > save_ptr++; > > if (!*save_ptr) return NULL; > char *token = save_ptr; > > // 查找下一个分隔符 > while (*save_ptr && !strchr(delim, *save_ptr)) > save_ptr++; > > if (*save_ptr) *save_ptr++ = '\0'; // 替换分隔符 > return token; > } > ``` ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值