【问题标题】:C: Parse empty tokens from a string with strtokC:使用 strtok 从字符串中解析空标记
【发布时间】:2010-07-30 21:36:03
【问题描述】:

我的应用程序生成如下字符串。我需要将分隔符之间的值解析为单个值。

2342|2sd45|dswer|2342||5523|||3654|Pswt

我正在使用strtok 循环执行此操作。对于第五个标记,我得到 5523。但是,我还需要考虑两个分隔符 || 之间的空值。根据我的要求,5523 应该是第六个令牌。

token = (char *)strtok(strAccInfo, "|");

for (iLoop=1;iLoop<=106;iLoop++) { 
            token = (char *)strtok(NULL, "|");
}

有什么建议吗?

【问题讨论】:

  • strtok() 可以说是 C 标准中最糟糕的事情。您可以编写自己的解析器。

标签: c string


【解决方案1】:

在这种情况下,我通常更喜欢 p2 = strchr(p1, '|') 循环,内部带有 memcpy(s, p1, p2-p1)。它速度快,不会破坏输入缓冲区(因此它可以与const char * 一起使用)并且非常便携(即使在嵌入式上)。

它也是可重入的; strtok 不是。 (顺便说一句:可重入与多线程无关。strtok 已经与嵌套循环中断。可以使用strtok_r,但它不那么便携。)

【讨论】:

  • 我使用了您的输入并更新了我的代码。谢谢!如果您有兴趣,我有我在下面使用的代码作为答案。
  • 谢谢,你的回答启发了我this
  • 抱歉 Patrick,您能否详细解释一下您的解决方案是如何工作的?我猜s 是原始字符串,但p1p2 是什么?
【解决方案2】:

这是strtok 的限制。设计者考虑到了以空格分隔的标记。 strtok 反正也没什么用;只需滚动您自己的解析器。 C FAQ has an example

【讨论】:

  • 我从您发布的链接中获得了一些有用的信息。谢谢!
【解决方案3】:

在第一次调用时,函数期望 一个 C 字符串作为 str 的参数,其 第一个字符用作 扫描令牌的起始位置。 在随后的调用中,函数 需要一个空指针并使用 在最后一个结束之后的位置 令牌作为新的起始位置 扫描。

确定开始和结束 令牌,函数首先扫描 从起始位置 第一个字符不包含在 分隔符(成为 令牌的开头)。进而 从这个开头开始扫描 第一个字符的标记 包含在分隔符中,它变成 令牌的结尾。

这就是说它会跳过任何'|'标记开头的字符。使 5523 成为您已经知道的第 5 个令牌。只是想我会解释原因(我必须自己查一下)。这也意味着您不会得到任何空令牌。

由于您的数据是以这种方式设置的,因此您有几个可能的解决方案:
1) 找出所有出现的 ||并替换为 | | (放一个空格)
2) 执行 strstr 5 次并找到第 5 个元素的开头。

【讨论】:

  • 感谢您的信息。希望下次我需要时我会记住这一点。 :-D 你的第一个解决方案有点搞砸了我的结果,因为字符串中有有效的组件返回管道之间的空格。第二种解决方案可能会变得乏味并且可能无法实现,因为不同数据集的字符串可能不同。
  • @Bash - 抱歉,我无法提供更多帮助 :(
  • 哦,你帮了很多忙……信息就是我们领域的力量,对吧?
【解决方案4】:
char *mystrtok(char **m,char *s,char c)
{
  char *p=s?s:*m;
  if( !*p )
    return 0;
  *m=strchr(p,c);
  if( *m )
    *(*m)++=0;
  else
    *m=p+strlen(p);
  return p;
}
  • 可重入
  • 线程安全
  • 严格符合 ANSI
  • 需要一个未使用的帮助指针来调用 上下文

例如

char *p,*t,s[]="2342|2sd45|dswer|2342||5523|||3654|Pswt";
for(t=mystrtok(&p,s,'|');t;t=mystrtok(&p,0,'|'))
  puts(t);

例如

char *p,*t,s[]="2,3,4,2|2s,d4,5|dswer|23,42||5523|||3654|Pswt";
for(t=mystrtok(&p,s,'|');t;t=mystrtok(&p,0,'|'))
{
  char *p1,*t1;
  for(t1=mystrtok(&p1,t,',');t1;t1=mystrtok(&p1,0,','))
    puts(t1);
}

你的工作:) 实现 char *c 作为参数 3

【讨论】:

    【解决方案5】:

    考虑改用 strsep:strsep reference

    【讨论】:

    • 哦,好吧 :-) 我的大部分编码都在 UNIX 上,现在肯定会派上用场 :-))以前从未听说过。
    【解决方案6】:

    使用strtok 以外的其他内容。它根本不打算做你所要求的。当我需要这个时,我通常使用strcspnstrpbrk 并自己处理其余的标记。如果你不介意像strtok 这样修改输入字符串,它应该很简单。至少马上,这样的事情似乎应该起作用:

    // Warning: untested code. Should really use something with a less-ugly interface.
    char *tokenize(char *input, char const *delim) { 
        static char *current;    // just as ugly as strtok!
        char *pos, *ret;
        if (input != NULL)
            current = input;
    
        if (current == NULL)
            return current;
    
        ret = current;
        pos = strpbrk(current, delim);
        if (pos == NULL) 
            current = NULL;
        else {
            *pos = '\0';
            current = pos+1;
        }
        return ret;
    }
    

    【讨论】:

    • 由于 OP 只搜索一个分隔符,strchr() 可以用来代替strpbrk()
    • 我做的有点不同。还是谢谢。
    【解决方案7】:

    Patrick Schlüter answer启发,我做了这个函数,它应该是线程安全的并且支持空标记并且不改变原始字符串

    char* strTok(char** newString, char* delimiter)
    {
        char* string = *newString;
        char* delimiterFound = (char*) 0;
        int tokLenght = 0;
        char* tok = (char*) 0;
    
        if(!string) return (char*) 0;
    
        delimiterFound = strstr(string, delimiter);
    
        if(delimiterFound){
            tokLenght = delimiterFound-string;
        }else{
            tokLenght = strlen(string);
        }
    
        tok = malloc(tokLenght + 1);
        memcpy(tok, string, tokLenght);
        tok[tokLenght] = '\0';
    
        *newString = delimiterFound ? delimiterFound + strlen(delimiter) : (char*)0;
    
        return tok;
    }
    

    你可以像这样使用它

    char* input = "1,2,3,,5,";
    char** inputP = &input;
    char* tok;
    while( (tok=strTok(inputP, ",")) ){
        printf("%s\n", tok);
    }
    

    假设输出

    1
    2
    3
    
    5
    

    我测试了它的简单字符串,但还没有在生产中使用它,并且也将它发布到code review,所以你可以看看其他人是怎么想的

    【讨论】:

    • 如果你在 Posix 机器上,你可以替换 'tok = malloc(tokLenght + 1); memcpy(tok,字符串,tokLenght); tok[tokLenght] = '\0';'` 只需 tok = strndup(string, tokLength);
    【解决方案8】:

    以下是现在对我有用的解决方案。感谢所有回复的人。

    我正在使用 LoadRunner。因此,一些不熟悉的命令,但我相信流程可以很容易理解。

    char strAccInfo[1024], *p2;
    int iLoop;
    
    Action() {  //This value would come from the wrsp call in the actual script.
        lr_save_string("323|90||95|95|null|80|50|105|100|45","test_Param");
    
        //Store the parameter into a string - saves memory. 
        strcpy(strAccInfo,lr_eval_string("{test_Param}"));
        //Get the first instance of the separator "|" in the string
        p2 = (char *) strchr(strAccInfo,'|');
    
        //Start a loop - Set the max loop value to more than max expected.
        for (iLoop = 1;iLoop<200;iLoop++) { 
    
            //Save parameter names in sequence.
            lr_param_sprintf("Param_Name","Parameter_%d",iLoop);
    
            //Get the first instance of the separator "|" in the string (within the loop).
            p2 = (char *) strchr(strAccInfo,'|');           
    
            //Save the value for the parameters in sequence. 
            lr_save_var(strAccInfo,p2 - strAccInfo,0,lr_eval_string("{Param_Name}"));   
    
            //Save string after the first instance of p2, as strAccInfo - for looping.
            strcpy(strAccInfo,p2+1);
    
            //Start conditional loop for checking for last value in the string.
            if (strchr(strAccInfo,'|')==NULL) {
                lr_param_sprintf("Param_Name","Parameter_%d",iLoop+1);
                lr_save_string(strAccInfo,lr_eval_string("{Param_Name}"));
                iLoop = 200;    
            }
        }
    }
    

    【讨论】:

    • 在某些时候,你需要解释为什么你有全局变量而不是局部变量,以及为什么你的函数没有返回类型(这是非常古老的 C 风格)。或者,更好的是,只需修复代码,以便在严格的编译器警告下干净地编译。使用iLoop = 200; 实现break; 是脆弱的。目前尚不清楚为什么在循环控制中使用 200。
    猜你喜欢
    • 2015-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多