使用strtok读取csv文件
我试图在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循环之外)是错误的,它打印整行内容。
我很困惑。 更改arrays时,如何在while循环后仍然打印正确的答案。
首先,介绍strtok()
工作原理。 该函数将返回指向原始字符串中某处的指针,该字符串已被修改,使其看起来像只有一个令牌(a) 。
例如, "A,B,C"
的第A
字符将把它变成"A\0B,C"
并返回A
字符的地址。 在那时使用它会给你"A"
。
类似地,第二个调用会将其变为"A\0B\0C"
并返回B
字符的地址。
它给你指向原始字符串的事实在这里是至关重要的,因为原始字符串位于buff
。
并且,每次从文件中读取一行时,您实际上都会覆盖 buff
。 因此,对于所有这五行, Game[i].ProductID
将只是buff
的第一个字符的地址。 处理完第五行后,该行:
while (fgets(buff, 1024, fp) != NULL && i < 5)
在退出循环之前 ,将首先读取第六行。
这就是为什么你看到的最后一行与前五行中的任何一行实际上都不一样。 你在buff
的(相同的)地址打印出ProductID
所有C字符串,所以你只能看到第六个,并且你看到了整行,因为你在阅读之后没有标记它。
您需要做的是在覆盖行之前复制令牌。 这可以用类似的东西来完成(它有点复杂,但正确处理strtok
返回NULL的情况):
if ((Game[i].ProductID = strtok(buff, ",")) != NULL) Game[i].ProductID = strdup(Game[i].ProductID);
记住你应该在某些时候free
那些内存分配。
在极不可能的事件中,您的环境没有strdup
(它是POSIX而不是ISO),请参阅此处 。
而且,除此之外,大多数CSV实现允许嵌入逗号,例如将它们括在引号中或转义它们(后者很少见,但我已经看过它们):
name,"diablo, pax",awesome name,diablo\, pax,awesome
这两个领域可能都是三个领域, name
, diablo, pax
和awesome
。
使用strtok
简化处理将不允许这样的复杂性,但是,假设您的字段不包含嵌入的逗号,则可能没问题。 如果您的输入更复杂,最好使用第三方CSV库(当然使用合适的许可证)。
(a)对于我们中的语言律师,这包含在ISO C标准中, C11 7.24.5.8 The strtok function, /3 and /4
(我的粗体):
3 /序列中的第一个调用搜索
s1
指向的字符串,查找s2
指向的当前分隔符字符串中未包含的第一个字符。 如果没有找到这样的字符,则s1
指向的字符串中没有标记,strtok
函数返回空指针。 如果找到这样的字符, 则它是第一个标记的开头 。4 /
strtok
函数然后从那里搜索当前分隔符字符串中包含的字符。 如果未找到此类字符,则当前标记将扩展到s1
指向的字符串的末尾,随后对标记的搜索将返回空指针。 如果找到这样的字符, 它将被空字符覆盖,该字符终止当前令牌。 strtok函数保存指向以下字符的指针,从该字符开始下一次搜索令牌。