我在编写学生成绩管理系统时用到了写文件和读文件,发现读文件总会显示出乱码,调试发现是因为用feof()做判断,在读文件最后时fread()多读取了一次。我一开始的解决办法是每次读取完文件,都把最后一个节点(每一行数据都读取放到链表的节点里)free掉。但这种方法明显不是那么好。
feof(fp)用来测试fp所指向的文件当前状态是否为“文件结束”。如果文件结束,则返回1,否则返回0。适合于二进制文件和文本文件。
但是,在实际使用feof时却发生了问题:文件实际已经结束时feof还要再判断一次才返回1
测试代码如下:
1 #include <stdio.h> 2 int main() 3 { 4 char ch = 0; 5 int i = 0; 6 FILE* fp; 7 // 建立空文件 8 fp = fopen("Info.txt", "w"); 9 if(NULL == fp) 10 { 11 printf("Cannot open file!\n"); 12 return 0; 13 } 14 fclose(fp); 15 // 读取空文件 16 fp = fopen("Info.txt", "r"); 17 if(NULL == fp) 18 { 19 printf("Cannot open file!\n"); 20 return 0; 21 } 22 while(!feof(fp)) 23 { 24 ch = fgetc(fp); 25 i++; 26 } 27 fclose(fp); 28 printf("fread times: %d\n" 29 "note: %c\n", i, ch); 30 return 0; 31 }
得到i为1,ch为空,说明feof()确实在文件为空状态时判断有误。
上网搜索有人说先读再判断就不会有问题了,但试了一下还是不行。对此,C FAQ-12.3的解释是“在C语言中,只有输入例程试图读并失败以后才能得到文件结束符。...fgets()在遇到文件结束符的时候返回NULL。实际上,在任何情况下,都完全没必要使用feof()。”
把while(!feof(fp)){}换成while(!fgets(str, 0, fp)){}确实能保证判断正确。但是,如果我在函数体里面还有其他读操作如fread()的话则会出现影响。所以,最好的解决办法是利用fseek()和ftell()配合来判断文件指针位置:
1 fseek(fp, 0L, SEEK_END);//文件指针置于结尾 2 len1 = ftell(fp);//获取结尾指针值 3 fseek(fp, 0L, SEEK_SET);//文件指针至于开头 4 len2 = ftell(fp);//获取开头指针值 5 while(len2 != len1)//循环判断 6 { 7 //do something 8 len2 = ftell(fp); 9 }
结果正确,无副作用。
其实还有另一种解决办法,就是每次写文件时在开头写入节点数,这样下次读文件时就知道要读取多少数据了。
ChinaUnix上也有一篇帖子对于这个问题讨论得很详细,但是提供的方法不适合我当时的情况:http://bbs.chinaunix.net/thread-957347-1-1.html