【问题标题】:Tokenizing strings in C在 C 中标记字符串
【发布时间】:2010-09-20 22:47:09
【问题描述】:

我一直在尝试使用 SPACE 作为分隔符来标记字符串,但它不起作用。有没有人建议它为什么不起作用?

编辑:使用标记化:

strtok(string, " ");

代码如下

pch = strtok (str," ");
while (pch != NULL)
{
  printf ("%s\n",pch);
  pch = strtok (NULL, " ");
}

【问题讨论】:

  • 您使用的是 strtok 还是您自己开发的东西? cplusplus.com/reference/clibrary/cstring/strtok.html 如果您使用 strtok,您是否尝试在常量字符串上执行此操作?
  • 您的示例将获得第一个令牌,请查看 gbjbaanb 或我的答案以了解正确用法。
  • 好的。现在我们正在取得进展。你认为你没有得到什么行为?
  • 顺便说一句,kombo。许多在服务台工作或教学的人将“它不起作用”这句话视为标记用户没有阅读提供的手册,或者不知道他们真正想要什么,或者非常困惑。你想要的形式是“我在做 X,我期望 Y,但我得到了 Z。怎么了?”
  • @dmckee:好点。规范外部参考:catb.org/~esr/faqs/smart-questions.html

标签: c string tokenize


【解决方案1】:

这是另一个strtok()实现,它可以识别连续的分隔符(标准库的strtok()没有这个)

该函数是 BSD 许可字符串库的一部分,称为 zString。非常欢迎您贡献:)

https://github.com/fnoyanisi/zString

char *zstring_strtok(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;
}

如前文所述,由于strtok() 或我在上面实现的那个依赖于static *char 变量来保存连续调用之间最后一个分隔符的位置,因此在处理多线程时应格外小心应用程序。

【讨论】:

    【解决方案2】:

    这是strtok 用法的示例,请记住strtok 会破坏其输入字符串(因此不能永远在字符串常量上使用

    char *p = strtok(str, " ");
    while(p != NULL) {
        printf("%s\n", p);
        p = strtok(NULL, " ");
    }
    

    基本上需要注意的是,将NULL 作为第一个参数传递给strtok 会告诉它从之前标记的字符串中获取下一个标记。

    【讨论】:

    • strtok 有一个内部状态变量来跟踪被标记化的字符串。当您将NULL 传递给它时,strtok 将继续使用此状态变量。当您传递一个非空值时,状态变量将被重置。换句话说:传递NULL 意味着“继续标记相同的字符串”。
    • 你是对的,这就是为什么许多实现提供strtok_r 至少提供了一种以线程安全的方式使用它的方法。
    • @Gnuey, p 将指向字符 in 被标记的字符串。此外,strtok'\0' 字符替换找到的分隔符,以便p 将有效地成为有效的NUL 终止字符串。因此,如果您要在 char[] s = "hello world"; 上运行它,第一次调用将返回指向 h 字符的指针,然后缓冲区将包含 "hello\0world"
    【解决方案3】:

    这样做:

    char s[256];
    strcpy(s, "one two three");
    char* token = strtok(s, " ");
    while (token) {
        printf("token: %s\n", token);
        token = strtok(NULL, " ");
    }
    

    注意:strtok 修改了它的标记化字符串,因此它不能是 const char*

    【讨论】:

      【解决方案4】:
      int not_in_delimiter(char c, char *delim){
      
          while(*delim != '\0'){
                  if(c == *delim) return 0;
                  delim++;
          }
          return 1;
      }
      
      char *token_separater(char *source, char *delimiter, char **last){
      
      char *begin, *next_token;
      char *sbegin;
      
      /*Get the start of the token */
      if(source)
        begin = source;
      else
        begin = *last;
      
      sbegin = begin;
      
      /*Scan through the string till we find character in delimiter. */
      while(*begin != '\0' && not_in_delimiter(*begin, delimiter)){
             begin++;
      }
      
      /* Check if we have reached at of the string */
      if(*begin == '\0') {
      /* We dont need to come further, hence return NULL*/
         *last = NULL;
          return sbegin;
      }
      /* Scan the string till we find a character which is not in delimiter */
       next_token  = begin;
       while(next_token != '\0' && !not_in_delimiter(*next_token, delimiter))    {
          next_token++;
       }
       /* If we have not reached at the end of the string */
       if(*next_token != '\0'){
        *last = next_token--;
        *next_token = '\0';
         return sbegin;
      }
      }
      
       void main(){
      
          char string[10] = "abcb_dccc";
          char delim[10] = "_";
          char *token = NULL;
          char *last = "" ;
          token  = token_separater(string, delim, &last);
          printf("%s\n", token);
          while(last){
                  token  = token_separater(NULL, delim, &last);
                  printf("%s\n", token);
          }
      

      }

      您可以在我的个人资料中提到的博客中阅读详细分析:)

      【讨论】:

      • 很好,@jitsceait,但是如果我在输入时将两个分隔符放在一起会发生什么?我会稍微修改一下你的代码。
      • 我想我已经为连续分隔符添加了一个测试用例,它正在工作。您能否突出显示您更改的代码?
      【解决方案5】:

      我创建了一些字符串函数来拆分值,尽可能使用更少的指针,因为此代码旨在在 PIC18F 处理器上运行。当可用的可用 RAM 很少时,这些处理器不能很好地处理指针:

      #include <stdio.h>
      #include <string.h>
      
      char POSTREQ[255] = "pwd=123456&apply=Apply&d1=88&d2=100&pwr=1&mpx=Internal&stmo=Stereo&proc=Processor&cmp=Compressor&ip1=192&ip2=168&ip3=10&ip4=131&gw1=192&gw2=168&gw3=10&gw4=192&pt=80&lic=&A=A";
      
      int findchar(char *string, int Start, char C) {
          while((string[Start] != 0)) { Start++; if(string[Start] == C) return Start; }
          return -1;
      }
      
      int findcharn(char *string, int Times, char C) {
         int i = 0, pos = 0, fnd = 0;
      
          while(i < Times) {
             fnd = findchar(string, pos, C);
              if(fnd < 0) return -1;
              if(fnd > 0) pos = fnd;
             i++;
         }
         return fnd;
      }
      
      void mid(char *in, char *out, int start, int end) {
          int i = 0;
          int size = end - start;
      
          for(i = 0; i < size; i++){
              out[i] = in[start + i + 1];
          }
          out[size] = 0;
      }
      
      void getvalue(char *out, int index) {
          mid(POSTREQ, out, findcharn(POSTREQ, index, '='), (findcharn(POSTREQ, index, '&') - 1));
      }
      
      void main() {
         char n_pwd[7];
         char n_d1[7];
      
         getvalue(n_d1, 1);
      
         printf("Value: %s\n", n_d1);
      } 
      

      【讨论】:

        【解决方案6】:

        strtok 可能非常危险。它不是线程安全的。它的预期用途是在循环中一遍又一遍地调用,传入前一个调用的输出。 strtok 函数有一个内部变量,用于存储 strtok 调用的状态。这种状态不是每个线程独有的——它是全局的。如果任何其他代码在另一个线程中使用 strtok,就会出现问题。也不是您想要追踪的问题!

        我建议寻找一个正则表达式实现,或者使用 sscanf 来拆分字符串。

        试试这个:

        char strprint[256];
        char text[256];
        strcpy(text, "My string to test");
        while ( sscanf( text, "%s %s", strprint, text) > 0 ) {
           printf("token: %s\n", strprint);
        }
        

        注意:'text' 字符串在分离时会被破坏。这可能不是首选行为 =)

        【讨论】:

        • 事实上,如果你看一下现代的 strtok 实现,它们倾向于使用线程本地存储(MSVC 多年来一直这样做),所以它们是线程安全的。不过,它仍然是一个过时的功能,我会避免......
        • strtok_r 是 strtok pubs.opengroup.org/onlinepubs/009695399/functions/strtok.html 的线程安全版本
        • 我同意第一段,但后面的句子很糟糕。 scanf 很难正确使用,如您的示例所示;您忘记传递大小 (%255s)。
        • 不过,strtok() 对于非线程遗留系统来说很好。复古系统的古老代码。
        【解决方案7】:

        您可以通过引入一个额外的变量来简化代码。

        #include <string.h>
        #include <stdio.h>
        
        int main()
        {
            char str[100], *s = str, *t = NULL;
        
            strcpy(str, "a space delimited string");
            while ((t = strtok(s, " ")) != NULL) {
                s = NULL;
                printf(":%s:\n", t);
            }
            return 0;
        }
        

        【讨论】:

          【解决方案8】:

          阅读 strtok 文档时,我发现您需要在第一次“初始化”调用之后传入一个 NULL 指针。也许你没有那样做。当然只是猜测。

          【讨论】:

            猜你喜欢
            • 2011-05-31
            • 2012-04-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-06-02
            相关资源
            最近更新 更多