8.2 EOF和FEOF的区别
通过上一节的讲解,读者对文件应该有了大概的认识,接下来介绍在文件操作中经常使用到的EOF和FEOF究竟有什么样的区别。
首先,读者需要明白EOF是C语言中的一个宏,相应的实现代码如下:
define EOF(-1)
EOF是end of file的缩写,其值为-1。从字面上也可以知道它以类作为判断文件结束的标志,但是需要注意的是,它只能作为文本文件的结束标志,因为文本文件的存储方式是将字符转换为ASCII码,而ASCII码中不会出现-1。EOF不仅可以用来检测文件是否结束,还可以用来判断函数是否调用成功,下面通过一段代码来看其具体的使用。
include<stdio.h>
void copy(FILEfpin,FILEfpout)
{
char ch;
ch=getc(fpin);
while(!feof(fpin))
{
putc(ch,fpout);
ch=getc(fpin);
}
return;
}
int main()
{
FILEfpin,fpout;
fpin=fopen("text1.txt","r");
fpout=fopen("text2.txt","w");
int c;
while((c=getchar())!=EOF)
{
putchar(c);
c=getchar();
}
copy(fpin,fpout);
fclose(fpin);
fclose(fpout);
return 0;
}
先在当前目录下建立一个text1.txt,在其中输入“hello world!”,由于要测试在按下Ctrl+C组合键之后代码是退出循环执行接下来的程序还是直接退出程序,因此在while循环的后面调用了一个copy函数,其功能是将text1.txt中的内容写入text2.txt中。运行代码,由于程序中有一个while循环,因此可以进行输入,只要输入的不是EOF,就会不停地执行while循环体,但是当按下Ctrl+C组合键时窗口就消失了。为了验证是否执行接下来的语句,打开当前目录后发现,目录下多了一个text2.txt,其中的内容和text 1.txt中的完全相同。所以,EOF不仅可以用来判断文本文件是否结束,还可以用来判断函数是否得到成功调用。
但是在二进制文件中,通常不采用EOF来判断是否到达文件的末端,因为二进制文件中会出现-1值,如果没有到达末端就出现了-1值,那么就会出现误判的情况。先来看下面的代码。
include<stdio.h>
void copy(FILEfpin,FILEfpout)
{
char ch;
while((ch=getc(fpin))!=EOF)
{
putc(ch,fpout);
}
return;
}
int main()
{
FILEfpin,fpout;
char b=0x34;
char a=0xff;
fpin=fopen("dat1.dat","wb");
fpout=fopen("dat2.dat","wb");
fputc(b,fpin);
fputc(a,fpin);
fputc(b,fpin);
fclose(fpin);
fpin=fopen("dat1.dat","rb");
copy(fpin,fpout);
fclose(fpin);
fclose(fpout);
return 0;
}
在上面的代码中,先以二进制文件的格式打开dat1.dat,对其进行写操作,写入的3个字符的ASCII码分别是0x34、0xff、0x34。然后再次打开该文件,对其进行读操作,将其中的字符读取到dat2.dat中。运行完程序后打开dat1.dat和dat2.dat,发现它们的内容并不一样,在dat1.dat中是“44”,而在dat2.dat中的是“4”,由此发现0xff被视为结束符,从而判定文件已到末端,不再对后面的数据进行读写操作,所以后面的数据丢失了。但如果适当修改上面的代码,那么同样可以用EOF作为二进制文件结束符的判断标准,修改方法是将函数copy中的“char ch”改为“short ch”,即:
void copy(FILEfpin,FILEfpout)
{
short ch;
while((ch=getc(fpin))!=EOF)
{
putc(ch,fpout);
}
return;
}
修改为以上代码之后再来看运行结果,这时发现能够完整地将dat1.dat中的数据写入到dat2.dat中去了。下面来分析其实现的原因,由于定义的是short型,占用2字节,读取的0xff实际变为0x00ff,这个值不再是-1(0xffff),所以可以得到完整的数据信息。
通常,对于二进制文件的操作都是采用feof宏来判断的。feof宏是一个带参数的宏,接下来介绍feof宏的实现。
define_IOEOF 0x0010
define feof(_stream)((_stream)->_flag&_IOEOF)
需要注意的是,文件指针的位置是由其中的结构体成员变量_ptr来标识的,只有文件指针已经到达文件的末端再进行读写操作时,其中的文件状态标识符才会被置为_IOEOF,这个时候再调用FEOF宏才会得到其返回值16,表示到了文件的末端。看下面的代码。
include<stdio.h>
int main()
{
FILEfpin,fpout;
char b=0x34;
fpin=fopen("dat1.dat","wb");
fputc(b,fpin);
fclose(fpin);
fpin=fopen("dat1.dat","rb");
char c;
while(!feof(fpin))
{
c=getc(fpin);
printf("%X\n",c);
}
fclose(fpin);
return 0;
}
运行结果:
34
FFFFFFFF
可以发现上面的运行结果中多了一个FFFFFFFF,正如前面介绍的,由于在读到最后一个字符时再次对文件进行读写操作,其中的文件状态标识符才会被置为_IOEOF,因此多输出了一个FFFFFFFF。适当修改上面的代码来去除FFFFFFFF,如下:
include<stdio.h>
int main()
{
FILEfpin,fpout;
char b=0x34;
fpin=fopen("dat1.dat","wb");
fputc(b,fpin);
fclose(fpin);
fpin=fopen("dat1.dat","rb");
char c;
c=getc(fpin);
while(!feof(fpin))
{
printf("%X\n",c);
c=getc(fpin);
}
fclose(fpin);
return 0;
}
运行结果:
34
所以,在使用FEOF的时候要尤其注意这点,否则就会在对文件进行读写操作时得到一些不需要的数据。
需要注意的是,FEOF不仅可以用来判断二进制文件是否结束,还可以用来判断文本文件是否结束。如果遇到文件结束符,那么FEOF的返回值为16,否则为0。可以通过下面的代码来测试。
include<stdio.h>
void main()
{
FILE*fp;
fp=fopen("text1.txt","r");
if(NULL==fp)
{
printf("打开失败!\n");
return;
}
char ch;
printf("feof作为文本文件结束的判断标志\n");
ch=getc(fp);
while(!feof(fp))
{
ch=getc(fp);
printf("%d\n",feof(fp));
}
fclose(fp);
fp=fopen("text2.txt","rb");
if(NULL==fp)
{
printf("打开失败!\n");
return;
}
printf("feof作为二进制文件结束的判断标志\n");
ch=getc(fp);
while(!feof(fp))
{
ch=getc(fp);
printf("%d\n",feof(fp));
}
fclose(fp);
return;
}
运行结果:
feof作为文本文件结束的判断标志
0
0
16
feof作为二进制文件结束的判断标志
0
0
16
在上面的代码中,将FEOF作为文本文件和二进制文件结束的判断标志。首先在当前的路径下建立两个文件text1.txt和text.txt,在两个文件中随机输入3个字符。分析运行结果,在没有到达文件末端的时候,文本文件和二进制文件都打印出0,在到达文件末端时打印出16,由此可知,使用FEOF宏可以作为二进制文件和文本文件结束的判断标志,没有结束时返回值为0,结束后返回值为16。