【问题标题】:How does strtok() split the string into tokens in C?strtok() 如何将字符串拆分为 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.

标签: c string split token strtok


【解决方案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 函数记录了您在调用它时首先提供的字符串。 (这对于多线程应用程序来说确实很危险)

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 2011-01-06
                        • 2020-05-24
                        • 1970-01-01
                        • 1970-01-01
                        • 2021-03-30
                        • 2013-08-21
                        • 1970-01-01
                        • 2013-09-15
                        相关资源
                        最近更新 更多