使用strtok读取csv文件

时间:2017-08-02 01:12:47

标签: c csv strtok

我试图在C中使用strtok来读取csv文件,并将内容存储到struct Game的数组中。 我的代码如下所示:

  FILE *fp;
  int i = 0;
  if((fp=fopen("Games.csv","r"))==NULL)
    {
      printf("Can't open file.\n");
      exit(1);
    }
  rewind(fp);
  char buff[1024]; 
  fgets(buff,1024,fp);
  char* delimiter = ",";

  while(fgets(buff, 1024, (FILE*)fp)!=NULL && i<5){

    Game[i].ProductID= strtok(buff, ",");   


    Game[i].ProductName = strtok(NULL, delimiter);

        Game[i].Publisher = strtok(NULL, delimiter);

    Game[i].Genre = strtok(NULL, delimiter);

    Game[i].Taxable = atoi(strtok(NULL, delimiter));

    Game[i].price = strtok(NULL, delimiter);

    Game[i].Quantity  = atoi(strtok(NULL, delimiter));


       printf("%s\n", Game[i].ProductID);

    i++;
   }


    i = 0;
    for(i = 0; i<5; i++){
       printf("%s", Game[i].ProductID);
    }

输出如下所示:

DS_25ROGVOIRY
DS_25MMD4N2BL
DS_258KADVNLH
DS_25UR7M375D
DS_25FP45CJFZ
DS_25AN1EA3PV,Blitz I: The League,Midway Games,Sports,0,$103.03 ,2
DS_25AN1EA3PV,Blitz I: The League,Midway Games,Sports,0,$103.03 ,2
DS_25AN1EA3PV,Blitz I: The League,Midway Games,Sports,0,$103.03 ,2
DS_25AN1EA3PV,Blitz I: The League,Midway Games,Sports,0,$103.03 ,2
DS_25AN1EA3PV,Blitz I: The League,Midway Games,Sports,0,$103.03 ,2

前五行(在while循环中)是正确的。但是,最后五行(while循环之外)是错误的,它会打印整行内容。

我很困惑。更改数组时如何在while循环后仍然打印正确的答案。

1 个答案:

答案 0 :(得分:3)

首先,介绍strtok()的工作原理。该函数将返回指向原始字符串中某处的指针,该字符串已被修改,使其看起来只有一个令牌(a)。 / p>

例如,strtok的第一个"A,B,C"会将其转换为"A\0B,C"并返回A个字符的地址。然后在那时使用它会给你"A"

同样,第二次调用会将其转换为"A\0B\0C"并返回B字符的地址。

它指向原始字符串的事实在这里是至关重要的,因为原始字符串位于buff

并且,每当您从文件中读取一行时,您实际上覆盖 buff。因此,对于所有这五行,Game[i].ProductID将只是buff的第一个字符的地址。处理完第五行后,行:

while (fgets(buff, 1024, fp) != NULL && i < 5)

将首先在退出循环之前的第六行中读取。

这就是为什么你看到的最后一行实际上与前五行中的任何一行相同。您在ProductID的(相同)地址打印出buff的所有C字符串,因此您只看到第六个,并且您看到已满行,因为你在阅读之后并没有将其标记为。

您需要做的是在覆盖行之前制作令牌的副本。这可以用类似的东西来完成(它有点复杂,但正确处理strtok返回NULL的情况):

if ((Game[i].ProductID = strtok(buff, ",")) != NULL)
    Game[i].ProductID = strdup(Game[i].ProductID);

记住你应该在某个时候free那些内存分配。

令人难以置信的不太可能发生的事件中,您的环境没有strdup(它的POSIX而不是ISO),请参阅here

而且,除此之外,大多数CSV实现允许使用嵌入式逗号,例如将它们括在引号中或转义它们(后者很少见,但我已经看到它们):

name,"diablo, pax",awesome
name,diablo\, pax,awesome

这两个字段可能需要三个字段namediablo, paxawesome

使用strtok进行简化处理不会产生这种复杂性,但假设您的字段不包含嵌入式逗号,则可能没问题。如果您的输入 更复杂,那么最好使用第三方CSV库(当然还有合适的许可证)。

(a)对于我们中的语言律师,ISO C标准C11 7.24.5.8 The strtok function, /3 and /4(我的大胆)中涵盖了这一点:

  

3 /序列中的第一个调用搜索s1指向的字符串,查找s2指向的当前分隔符字符串中包含的 not 的第一个字符。如果找不到这样的字符,则s1指向的字符串中没有标记,strtok函数返回空指针。如果找到这样的字符, 它就是第一个标记的开头

     

4 / strtok函数然后从那里搜索当前分隔符字符串中包含 的字符。如果未找到此类字符,则当前标记将扩展到s1指向的字符串的末尾,随后对标记的搜索将返回空指针。如果找到这样的字符, 它将被一个空字符覆盖,该字符终止当前令牌。 strtok函数保存一个指向下一个字符的指针,从该字符开始将开始搜索令牌。