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 中的标记?的主要内容,如果未能解决你的问题,请参考以下文章

strtok — 标记分割字符串

strtok — 标记分割字符串

C 标记字符串

使用strtok从字符串中解析空标记

Strtok_r 返回 NULL

浅谈strtok