c ++ strtok跳过第二个令牌或连续分隔符

时间:2014-10-23 06:43:43

标签: c++ c

我正在尝试读取CSV逗号分隔文件,文件内容为

      one,,three

读取文件的代码就是这个......

      inFile.getline(line, 500);                            
      token1 = strtok(line, ",");
      token2 = strtok(NULL, ",");
      token3 = strtok(NULL, ",");

      if(token1 != NULL){
             cout << "token1 = " << token1 << "\n";      
      }else{ 
             cout << "token1 = null\n" ;
      }
      if(token2 != NULL){ 
             cout << "token2 = " << token2 << "\n";      
      }else{ 
             cout << "token2 = null\n" ;
      } 
      if(token3 != NULL){ 
             cout << "token3 = " << token3 << "\n";      
      }else{ 
             cout << "token3 = null\n";
      }

输出就是这个

token1 = one
token2 = three
token3 = null

而我的期望是输出应该是这样的......

token1 = one
token2 = null
token3 = three

我确实改变了来自

的陈述
     if(token1 != NULL) 

     if(token1)

但它也不起作用。

查看此示例http://www.cplusplus.com/reference/cstring/strtok/后,我已更新

   token2 = strtok(NULL, ",");

   token2 = strtok(NULL, ",,");

它也不起作用

5 个答案:

答案 0 :(得分:5)

从标准(C99,C ++ 11引用兼容性功能):

  

序列中的第一个调用将搜索s1指向的字符串,以查找 s2指向的当前分隔符字符串中未包含的第一个字符。

     

每个后续调用,使用空指针作为第一个参数的值,从保存的指针开始搜索,其行为如上所述。

这意味着,在查找第二个令牌时,它首先会跳过与分隔符字符串中的任何字符匹配的所有字符。因此,,,被视为输入字符串中的单个分隔符。

如果您希望令牌制作者的工作方式与标准制作者的工作方式不同,那么您不得不在其他地方查找,例如下面的代码:

#include <string.h>

char *paxtok (char *str, char *seps) {
    static char *tpos, *tkn, *pos = NULL;
    static char savech;

    // Specific actions for first and subsequent calls.

    if (str != NULL) {
        // First call, set pointer.

        pos = str;
        savech = 'x';
    } else {
        // Subsequent calls, check we've done first.

        if (pos == NULL)
            return NULL;

        // Then put character back and advance.

        while (*pos != '\0')
            pos++;
        *pos++ = savech;
    }

    // Detect previous end of string.

    if (savech == '\0')
        return NULL;

    // Now we have pos pointing to first character.
    // Find first separator or nul.

    tpos = pos;
    while (*tpos != '\0') {
        tkn = strchr (seps, *tpos);
        if (tkn != NULL)
            break;
        tpos++;
    }

    savech = *tpos;
    *tpos = '\0';

    return pos;
}

结合以下测试程序,应该为您提供所需的内容::

#include <stdio.h>

int usage (char *reason) {
    fprintf (stderr, "ERROR: %s.\n", reason);
    fprintf (stderr, "Usage: testprog <string> <separator>\n");
    return 1;
}

int main (int argc, char *argv[]) {
    if (argc != 3)
        return usage ("wrong number of parameters");

    printf ("Initial string is '%s'\n", argv[1]);

    char *token = paxtok (argv[1], argv[2]);
    while (token != NULL) {
        printf ("Token is '%s'\n", token);
        token = paxtok (NULL, argv[2]);
    }

    printf ("Final string is '%s'\n", argv[1]);

    return 0;
}

它提供了一个完整的程序,以便您可以测试它,例如使用命令:

testprog ,_start,,middle_,end, _,

将使用第二个中的两个分隔符,下划线和逗号来标记第一个字符串。它的输出显示它是如何工作的,你可以看到它拾取空标记,包括在开始和结束时:

Initial string is ',_start,,middle_,end,'
Token is ''
Token is ''
Token is 'start'
Token is ''
Token is 'middle'
Token is ''
Token is 'end'
Token is ''
Final string is ',_start,,middle_,end,'

请记住,使用静态,它受到与strtok相同的限制 - 您无法并排执行两个令牌化操作。你可以制作一个paxtok_r镜像strtok_r,但我会把它作为读者的练习。

答案 1 :(得分:5)

我在阅读CSV逗号分隔文件时遇到此问题。但是我们不能使用strtok()作为解决方案来解决分隔符字符连续出现的问题。因为按照标准

  

序列中的第一个调用搜索字符串       s1指向的第一个字符 not       包含在s2指向的当前分隔符字符串中。       如果没有找到这样的字符,那么就没有令牌       s1指向的字符串和strtok函数返回       空指针。如果找到这样的角色,那就是       开始第一个令牌。 C11§7.24.5.83

因此,对于我的情况,我使用strpbrk()函数定义了另一个解决方案,这对您也很有用。

#include<iostream.h>

char *strtok_new(char * string, char const * delimiter){
   static char *source = NULL;
   char *p, *riturn = 0;
   if(string != NULL)         source = string;
   if(source == NULL)         return NULL;

   if((p = strpbrk (source, delimiter)) != NULL) {
      *p  = 0;
      riturn = source;
      source = ++p;
   }
return riturn;
}

int main(){
   char string[] = "one,,three,";
   char delimiter[] = ",";
   char * p    = strtok_new(string, delimiter);

   while(p){
            if(*p)  cout << p << endl;
            else    cout << "No data" << endl;                
            p = strtok_new(NULL, delimiter);
   }
   system("pause");
   return 0;
}

<强>输出

one
No data
three

希望这是你想要的输出。

答案 2 :(得分:2)

http://www.cplusplus.com/reference/cstring/strtok/说:

  

要确定令牌的开头和结尾,该函数首先从起始位置扫描分隔符中包含的第一个字符(后者成为令牌的开头)。然后从分隔符中包含的第一个字符开始,从开头的分区开始扫描,这将成为令牌端。如果找到终止空字符,扫描也会停止。

因此,当函数'扫描(...)分隔符'中包含的第一个字符而不是时,它会跳过任何分隔符字符序列。这使得它无法检测连续分隔符之间的“空标记”。您必须自己扫描输入字符串char-by-char。

答案 3 :(得分:2)

使用函数strsep代替strtok,该函数将多个定界符视为空标记并返回所有标记。

strtok不同,您不必使用第一个空参数调用strsep。您可以这样称呼它:

char *string = "this,is,the,string,,,,you,want,to,parse";
char *token;

while (token = strsep(&string, ","))
{
     printf("%s\n", token);
}

如果这使您感到紧张或引发编译器警告,则始终可以显式检查NULL:

while ((token = strsep(&string, ",") != NULL))

某些旧的编译器库没有strsep

答案 4 :(得分:0)

此版本经过改进且可重入:

char *strtok_new_r(char * string, char const * delimiter, char **saveptr) {

    char *ptr, *riturn = 0;

    if (string != NULL) {
        *saveptr = string;
    }

    if (*saveptr == NULL) {
        return NULL;
    }

    if ((ptr = strpbrk(*saveptr, delimiter)) != NULL) {
        *ptr = 0;
        riturn = *saveptr;
        *saveptr = ++ptr;
    }

    if (!ptr) {
        if (*saveptr) {
            riturn = *saveptr;
            *saveptr = NULL;
        }
    }

    return riturn;
}