strtok() 如何将字符串拆分为 C 中的标记?
Posted
技术标签:
【中文标题】strtok() 如何将字符串拆分为 C 中的标记?【英文标题】:How does strtok() split the string into tokens in C? 【发布时间】:2011-04-22 20:16:07 【问题描述】:请向我解释strtok()
函数的工作原理。手册说它将字符串分解为标记。我无法从手册中理解它的实际作用。
我在str
和*pch
上添加了手表来检查它在第一个while 循环发生时的工作情况,str
的内容只是“this”。下面显示的输出是如何打印在屏幕上的?
/* strtok example */
#include <stdio.h>
#include <string.h>
int main ()
char str[] ="- This, a sample string.";
char * pch;
printf ("Splitting string \"%s\" into tokens:\n",str);
pch = strtok (str," ,.-");
while (pch != NULL)
printf ("%s\n",pch);
pch = strtok (NULL, " ,.-");
return 0;
输出:
拆分字符串“- 这是一个示例字符串。”进入令牌: 这 一种 样本 细绳【问题讨论】:
strtok()
通过在返回前用 NUL 终止标记来修改其参数字符串。如果您尝试检查整个缓冲区 (str[]),您会看到它在连续调用 strtok()
之间被修改。
不要看str
,而是看str[0]
, str[1]
, str[2]
, ...
@pmg: 我看了 str[0] 和 str[1].str[1] 应该是 '\0',但那里是个空格。
老实说,我从来没有费心去检查,但我想它存储了最后一个传入的指针,以及它离开的位置。如果指针为NULL就可以继续,否则清空重新开始。
@Firegun: static variable.
【参考方案1】:
strtok
正在用'\0'
给定字符串中的 NULL 字符替换分隔符
代码
#include<iostream>
#include<cstring>
int main()
char s[]="30/4/2021";
std::cout<<(void*)s<<"\n"; // 0x70fdf0
char *p1=(char*)0x70fdf0;
std::cout<<p1<<"\n";
char *p2=strtok(s,"/");
std::cout<<(void*)p2<<"\n";
std::cout<<p2<<"\n";
char *p3=(char*)0x70fdf0;
std::cout<<p3<<"\n";
for(int i=0;i<=9;i++)
std::cout<<*p1;
p1++;
输出
0x70fdf0 // 1. address of string s
30/4/2021 // 2. print string s through ptr p1
0x70fdf0 // 3. this address is return by strtok to ptr p2
30 // 4. print string which pointed by p2
30 // 5. again assign address of string s to ptr p3 try to print string
30 4/2021 // 6. print characters of string s one by one using loop
标记字符串之前
我将字符串 s 的地址分配给某个 ptr(p1) 并尝试通过该 ptr 打印字符串并打印整个字符串。
标记化后
strtok 将字符串 s 的地址返回给 ptr(p2) 但是当我尝试通过 ptr 打印字符串时它只打印“30”它没有打印整个字符串。所以可以肯定strtok is not just returning adress but it is placing '\0' character where delimiter is present
。
交叉检查
1.
我再次将字符串 s 的地址分配给某个 ptr (p3) 并尝试打印字符串,它打印“30”,因为在分隔符处使用 '\0' 更新字符串的标记。
2.
通过循环查看逐个字符打印字符串,第一个分隔符被替换为'\0',因此它打印的是空格而不是''
【讨论】:
【参考方案2】:所以,这是一个代码 sn-p 以帮助更好地理解这个主题。
打印令牌
任务:给定一个句子 s,将句子的每个单词打印在一个新行中。
char *s;
s = malloc(1024 * sizeof(char));
scanf("%[^\n]", s);
s = realloc(s, strlen(s) + 1);
//logic to print the tokens of the sentence.
for (char *p = strtok(s," "); p != NULL; p = strtok(NULL, " "))
printf("%s\n",p);
输入: How is that
结果:
How
is
that
解释:所以这里使用了“strtok()”函数,并使用for循环进行迭代以在单独的行中打印标记。
该函数将参数作为“字符串”和“断点”,并在这些断点处断开字符串并形成标记。现在,这些令牌存储在“p”中,并用于进一步打印。
【讨论】:
我认为通过一个例子来解释比参考一些文档要好得多。【参考方案3】:如果你发现它只是打印新行,你可以扫描字符数组来寻找令牌,否则打印字符。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
char *s;
s = malloc(1024 * sizeof(char));
scanf("%[^\n]", s);
s = realloc(s, strlen(s) + 1);
int len = strlen(s);
char delim =' ';
for(int i = 0; i < len; i++)
if(s[i] == delim)
printf("\n");
else
printf("%c", s[i]);
free(s);
return 0;
【讨论】:
【参考方案4】:strtok()
将字符串划分为标记。即从任何一个分隔符到下一个分隔符将是您的一个令牌。在您的情况下,起始标记将来自“-”并以下一个空格“”结束。然后下一个标记将从“”开始并以“,”结束。在这里你得到“这个”作为输出。类似地,字符串的其余部分被拆分为从一个空间到另一个空间的标记,最后以“。”结束最后一个标记
【讨论】:
一个token的结束条件成为下一个token的开始token?是否也有nul字符代替结束条件? @fahad- 是的,正如其他人所建议的那样,您拥有的所有分隔符都将替换为 NUL 字符。 如果所有的分隔符都被Nul替换了,那为什么字符串会包含“-this”呢?它应该包含“\0” @fahad - 它只用 NUL 替换分隔符,而不是分隔符之间的所有字符。它将字符串拆分为多个标记。您会得到“This”,因为它位于两个指定的分隔符之间,而不是“-this”。 @Fahad - 是的,绝对的。据我所知,所有空格、“、”和“-”都被 NUL 替换,因为您已将它们指定为分隔符。【参考方案5】:对于那些仍然难以理解这个 strtok()
函数的人,看看这个 pythontutor example,它是可视化 C(或 C++、Python ...)代码的好工具。
如果链接损坏,请粘贴:
#include <stdio.h>
#include <string.h>
int main()
char s[] = "Hello, my name is? Matthew! Hey.";
char* p;
for (char *p = strtok(s," ,?!."); p != NULL; p = strtok(NULL, " ,?!."))
puts(p);
return 0;
积分转到Anders K.
【讨论】:
【参考方案6】:strtok() 将指针存储在你上次离开的静态变量中,所以在第二次调用时,当我们传递 null 时,strtok() 从静态变量中获取指针。
如果您提供相同的字符串名称,它会再次从头开始。
此外,strtok() 具有破坏性,即它会更改原始字符串。所以请确保您始终拥有一份原始副本。
使用 strtok() 的另一个问题是,由于它将地址存储在静态变量中,因此在多线程编程中多次调用 strtok() 会导致错误。为此使用 strtok_r()。
【讨论】:
【参考方案7】:这就是我实现 strtok 的方式,虽然不是很好,但经过 2 小时的努力,它终于成功了。它确实支持多个分隔符。
#include "stdafx.h"
#include <iostream>
using namespace std;
char* mystrtok(char str[],char filter[])
if(filter == NULL)
return str;
static char *ptr = str;
static int flag = 0;
if(flag == 1)
return NULL;
char* ptrReturn = ptr;
for(int j = 0; ptr != '\0'; j++)
for(int i=0 ; filter[i] != '\0' ; i++)
if(ptr[j] == '\0')
flag = 1;
return ptrReturn;
if( ptr[j] == filter[i])
ptr[j] = '\0';
ptr+=j+1;
return ptrReturn;
return NULL;
int _tmain(int argc, _TCHAR* argv[])
char str[200] = "This,is my,string.test";
char *ppt = mystrtok(str,", .");
while(ppt != NULL )
cout<< ppt << endl;
ppt = mystrtok(NULL,", .");
return 0;
【讨论】:
【参考方案8】:这是我的实现,它使用哈希表作为分隔符,这意味着它是 O(n) 而不是 O(n^2) (here is a link to the code):
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define DICT_LEN 256
int *create_delim_dict(char *delim)
int *d = (int*)malloc(sizeof(int)*DICT_LEN);
memset((void*)d, 0, sizeof(int)*DICT_LEN);
int i;
for(i=0; i< strlen(delim); i++)
d[delim[i]] = 1;
return d;
char *my_strtok(char *str, char *delim)
static char *last, *to_free;
int *deli_dict = create_delim_dict(delim);
if(!deli_dict)
/*this check if we allocate and fail the second time with entering this function */
if(to_free)
free(to_free);
return NULL;
if(str)
last = (char*)malloc(strlen(str)+1);
if(!last)
free(deli_dict);
return NULL;
to_free = last;
strcpy(last, str);
while(deli_dict[*last] && *last != '\0')
last++;
str = last;
if(*last == '\0')
free(deli_dict);
free(to_free);
deli_dict = NULL;
to_free = NULL;
return NULL;
while (*last != '\0' && !deli_dict[*last])
last++;
*last = '\0';
last++;
free(deli_dict);
return str;
int main()
char * str = "- This, a sample string.";
char *del = " ,.-";
char *s = my_strtok(str, del);
while(s)
printf("%s\n", s);
s = my_strtok(NULL, del);
return 0;
【讨论】:
【参考方案9】:要了解strtok()
的工作原理,首先需要知道static variable 是什么。 This link 解释的很好......
strtok()
操作的关键是在连续调用之间保留最后一个分隔符的位置(这就是为什么strtok()
在使用null pointer
调用它时继续解析传递给它的原始字符串的原因在连续通话中)..
看看我自己的strtok()
实现,称为zStrtok()
,它的功能与strtok()
提供的功能略有不同
char *zStrtok(char *str, const char *delim)
static char *static_str=0; /* var to store last address */
int index=0, strlength=0; /* integers for indexes */
int found = 0; /* check if delim is found */
/* delimiter cannot be NULL
* if no more char left, return NULL as well
*/
if (delim==0 || (str == 0 && static_str == 0))
return 0;
if (str == 0)
str = static_str;
/* get length of string */
while(str[strlength])
strlength++;
/* find the first occurance of delim */
for (index=0;index<strlength;index++)
if (str[index]==delim[0])
found=1;
break;
/* if delim is not contained in str, return str */
if (!found)
static_str = 0;
return str;
/* check for consecutive delimiters
*if first char is delim, return delim
*/
if (str[0]==delim[0])
static_str = (str + 1);
return (char *)delim;
/* terminate the string
* this assignmetn requires char[], so str has to
* be char[] rather than *char
*/
str[index] = '\0';
/* save the rest of the string */
if ((str + index + 1)!=0)
static_str = (str + index + 1);
else
static_str = 0;
return str;
这是一个示例用法
Example Usage
char str[] = "A,B,,,C";
printf("1 %s\n",zStrtok(s,","));
printf("2 %s\n",zStrtok(NULL,","));
printf("3 %s\n",zStrtok(NULL,","));
printf("4 %s\n",zStrtok(NULL,","));
printf("5 %s\n",zStrtok(NULL,","));
printf("6 %s\n",zStrtok(NULL,","));
Example Output
1 A
2 B
3 ,
4 ,
5 C
6 (null)
代码来自a string processing library I maintain on Github,叫做zString。看看代码,甚至贡献:) https://github.com/fnoyanisi/zString
【讨论】:
【参考方案10】:strtok 运行时函数是这样工作的
第一次调用 strtok 时,您提供了一个要标记化的字符串
char s[] = "this is a string";
在上面的字符串空间中似乎是单词之间的一个很好的分隔符,所以让我们使用它:
char* p = strtok(s, " ");
现在发生的是搜索's'直到找到空格字符,返回第一个标记('this')并且p指向那个标记(字符串)
为了获得下一个令牌并继续使用相同的字符串 NULL 作为第一个传递 参数,因为 strtok 维护 一个静态指针 指向您之前传递的字符串:
p = strtok(NULL," ");
p 现在指向'is'
以此类推,直到找不到更多空格,然后将最后一个字符串作为最后一个标记“字符串”返回。
更方便的是,你可以这样写来打印出所有标记:
for (char *p = strtok(s," "); p != NULL; p = strtok(NULL, " "))
puts(p);
编辑:
如果您想存储来自strtok
的返回值,您需要将令牌复制到另一个缓冲区,例如strdup(p);
因为原始字符串(由 strtok
中的静态指针指向)在迭代之间被修改以返回令牌。
【讨论】:
所以它实际上并没有在字符串之间放置一个nul字符?为什么我的手表显示字符串只剩下“THIS”? 它确实将找到的 ' ' 替换为 '\0'。而且,它不会在以后恢复 ' ',所以你的字符串永远被破坏了。 +1 表示静态缓冲区,这是我不明白的 一个非常重要的细节,“返回第一个令牌,p
指向那个令牌”这一行缺少一个非常重要的细节,即strtok
需要改变原始的通过放置一个空字符代替分隔符来创建字符串(否则其他字符串函数将不知道令牌的结束位置)。它还使用静态变量跟踪状态。
@Groo 我想我已经在 2017 年所做的编辑中添加了这一点,但你是对的。【参考方案11】:
strtok
维护一个静态的内部引用,指向字符串中的下一个可用标记;如果您向它传递一个 NULL 指针,它将从该内部引用中工作。
这就是strtok
不能重入的原因;一旦你向它传递一个新指针,旧的内部引用就会被破坏。
【讨论】:
旧的内部参考“被破坏”是什么意思。你的意思是“覆盖”吗? @ylun.ca:是的,我就是这个意思。【参考方案12】:strtok
不会更改参数本身 (str
)。它存储该指针(在本地静态变量中)。然后,它可以在后续调用中更改该参数 指向 的内容,而无需将参数传回。 (它可以推进它保留的指针,但它需要执行它的操作。)
来自 POSIX strtok
页面:
此函数使用静态存储来跟踪调用之间的当前字符串位置。
有一个线程安全的变体 (strtok_r
) 不会做这种魔法。
【讨论】:
好吧,C 库函数可以追溯到很久以前,线程根本不在图片中(就 C 标准而言,它只是在 2011 年才开始存在),所以重新- 入学并不重要(我猜)。该静态局部使函数“易于使用”(对于“简单”的某些定义)。就像ctime
返回一个静态字符串 - 实用(没有人需要想知道谁应该释放它),但如果你不是很清楚它不会重入和绊倒你。
这是错误的:“strtok
不会更改参数本身 (str
)。” puts(str);
打印“- This”,因为 strtok
修改了 str
。
@MarredCheese:再读一遍。它不会修改指针。它修改指针指向的数据(即字符串数据)
哦,好吧,我没有意识到这就是你的意思。同意。【参考方案13】:
strtok 将标记一个字符串,即将其转换为一系列子字符串。
它通过搜索分隔这些标记(或子字符串)的分隔符来做到这一点。并且您指定分隔符。在你的情况下,你想要''或','或'。或“-”作为分隔符。
提取这些标记的编程模型是您手动 strtok 主字符串和分隔符集。然后你反复调用它,每次 strtok 都会返回它找到的下一个标记。直到它到达主字符串的末尾,当它返回 null 时。另一个规则是您只在第一次传递字符串,而在随后的时间传递 NULL。这是一种告诉 strtok 是否正在使用新字符串开始新的标记化会话,或者您正在从先前的标记化会话中检索标记的方法。请注意,strtok 会记住其标记化会话的状态。由于这个原因,它不是可重入的或线程安全的(你应该使用 strtok_r 代替)。要知道的另一件事是它实际上修改了原始字符串。它为找到的分隔符写入 '\0'。
一种调用 strtok 的方法简而言之如下:
char str[] = "this, is the string - I want to parse";
char delim[] = " ,-";
char* token;
for (token = strtok(str, delim); token; token = strtok(NULL, delim))
printf("token=%s\n", token);
结果:
this
is
the
string
I
want
to
parse
【讨论】:
【参考方案14】:strtok 将第二个参数中的字符替换为 NULL,NULL 字符也是字符串的结尾。
http://www.cplusplus.com/reference/clibrary/cstring/strtok/
【讨论】:
【参考方案15】:strtok 修改其输入字符串。它将空字符 ('\0') 放入其中,以便将原始字符串的位作为标记返回。实际上 strtok 不分配内存。如果将字符串绘制为一系列框,您可能会更好地理解它。
【讨论】:
【参考方案16】:第一次调用它时,将要标记的字符串提供给strtok
。然后,要获得以下标记,只需将 NULL
赋予该函数,只要它返回非 NULL
指针即可。
strtok
函数记录了您在调用它时首先提供的字符串。 (这对于多线程应用程序来说确实很危险)
【讨论】:
以上是关于strtok() 如何将字符串拆分为 C 中的标记?的主要内容,如果未能解决你的问题,请参考以下文章