【问题标题】:Use strtok read csv file使用 strtok 读取 csv 文件
【发布时间】:2017-08-02 01:12:47
【问题描述】:

我正在尝试使用 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循环后如何仍然打印正确的答案。

【问题讨论】:

  • 与你的问题无关,strtok可以给你解析csvs的意外行为。即 "a,b,,c,d" 将解析为 4 个值而不是 5 个。
  • CSV 可以在字段中嵌入逗号。例如,zero,"one,two",three 是三个字段。 strtok() 无法处理。

标签: c csv strtok


【解决方案1】:

首先,了解strtok() 的工作原理。该函数将返回一个指向 original 字符串中某处的指针,该字符串已被修改以使其看起来只有一个标记 (a)

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

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

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

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

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

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

这就是为什么您看到的最后几行实际上与前五行中的任何一行相同。您正在打印 ProductID 的所有 C 字符串,位于 buff 的(相同)地址,所以您只看到第六个,并且您看到 full 行,因为您没有读完后不要标记那个。

您需要做的是在覆盖该行之前制作令牌的副本。这可以通过以下方式来完成(它有点复杂,但可以正确处理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 函数保存一个指向下一个字符的指针,下一个字符将从该指针开始将开始搜索令牌。

【讨论】:

  • 谢谢你的回答,对我很有帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-02
  • 2018-05-24
  • 2015-06-12
相关资源
最近更新 更多