我试图在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循环后仍然打印正确的答案。
答案 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
这两个字段可能需要三个字段name
,diablo, pax
和awesome
。
使用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函数保存一个指向下一个字符的指针,从该字符开始将开始搜索令牌。