为什么用feof()或者iostream::eof来控制循环是不好的
例1:
data.txt为:
data.txt十六进制如下:
可看出结尾有一个换行符(注:Windows下换行符为\r\n)。
#include <stdio.h>
int main()
{
FILE *fp;
int i = 0;
char s[3][7];
if ((fp = fopen("data.txt", "r")) == NULL)
printf("Error: can't open file\n");
else {
while (!feof(fp)) {
fgets(s[i], 7, fp); // 读取一行 循环第一次读入后s[0][0]='1',s[0][1]=' ',...s[0][5]='\n',s[0][6]='\0'
printf("%s", s[i]); // 输出
i++;
}
printf("\n");
printf("i = %d\n", i);
fclose(fp);
}
return 0;
}
运行结果为:
结果分析:
注意:fgets函数遇到换行符时,会将换行符读入,并紧跟换行符后加入一个'\0'
在stdio.h中feof的定义如下:
#define _IOEOF 0x0010
#define feof(_stream) ((_stream)->_flag & _IOEOF)
由此可知,只有当fp->_flag & 0x0010为真时,才认为文件结束了。只有当file position indicator(在Windows上是fp->_ptr)到了文件末尾,然后再发生读/写操作时,fp->_flag才会被置为含有 _IOEOF,然后再调用feof(),才会得到文件结束的信息。并不是file position indicator一指到文件尾,feof()就认为文件结束。所以该例中会第4次进入循环体,而第4次执行fgets(s[i], 7, fp);时,fp已指向文件尾,所以并没有任何数据读入s[3],s[3]为乱码(下标已越界)。
循环条件可以用feof,但必须记住:当文件刚刚读到文件尾时,feof不会返回true。只有在文件尾部再次进行一次读操作,feof才会返回真。
可对例1做如下几种修改:
修改1:循环体内加入一行 if (!feof(fp))判断
#include <stdio.h>
int main()
{
FILE *fp;
int i = 0;
char s[3][7];
if ((fp = fopen("data.txt", "r")) == NULL)
printf("Error: can't open file\n");
else {
while (!feof(fp)) {
fgets(s[i], 7, fp); // 读取一行 循环第一次读入后s[0][0]='1',s[0][1]=' ',...s[0][5]='\n',s[0][6]='\0'
if (!feof(fp)) // 加入此行
printf("%s", s[i]); // 输出
i++;
}
printf("\n");
printf("i = %d\n", i);
fclose(fp);
}
return 0;
}
输出:
修改2:将data.txt文件末尾的换行符去掉
即data.txt 为:
十六进制如下:
运行例1中代码,输出如下:
说明结尾没有换行符,则第3次循环即遇到了文件尾。
注:Linux中的Vim结尾会自动加一个换行符\n,
去除vim文件尾的默认换行符:
1.vim -b filename
2.命令行模式:set noeol (注:noeol = no end-of-line)
修改3:将fgets做为循环条件
#include <stdio.h>
int main()
{
FILE *fp;
int i = 0;
char s[3][7];
if ((fp = fopen("data.txt", "r")) == NULL)
printf("Error: can't open file\n");
else {
while (fgets(s[i], 7, fp) != NULL) {
printf("%s", s[i]); // 输出
i++;
}
printf("\n");
printf("i = %d\n", i);
fclose(fp);
}
return 0;
}
输出:
建议使用修改3。
例2:
1.txt为:
例2中是逐个读取字符,因此1.txt结尾有无换行符结果都一样。
#include <iostream>
#include <fstream>
#include <cassert>
#include <string>
using namespace std;
// 逐个字符读入(忽略空格与回车)
void readtxt(string file)
{
ifstream infile;
infile.open(file.data()); // 将文件流对象与文件连接起来
assert(infile.is_open()); // 若失败,则输出错误消息,并终止程序运行
while (!infile.eof())
{
char c;
infile >> c; // 类似cin>>。 c = infile.get();和 infile.get(c);效果一模一样!
cout << c << endl;
}
infile.close(); // 关闭文件输入流
}
int main()
{
readtxt("1.txt");
return 0;
}
输出:
将char c;修改为char c = 65;
#include <iostream>
#include <fstream>
#include <cassert>
#include <string>
using namespace std;
// 逐个字符读入(忽略空格与回车)
void readtxt(string file)
{
ifstream infile;
infile.open(file.data()); // 将文件流对象与文件连接起来
assert(infile.is_open()); // 若失败,则输出错误消息,并终止程序运行
while (!infile.eof())
{
char c = 65; // 将char c;修改为char c = 65;
infile >> c; // 类似cin>>。 c = infile.get();和 infile.get(c);效果一模一样!
cout << c << endl;
}
infile.close(); // 关闭文件输入流
}
int main()
{
readtxt("1.txt");
return 0;
}
输出:
可看出第5次循环执行 infile >> c; 后,对c无任何影响,c维持上次状态。
对例2进行如下修改:
#include <iostream>
#include <fstream>
#include <cassert>
#include <string>
using namespace std;
// 逐个字符读入(忽略空格与回车)
void readtxt(string file)
{
ifstream infile;
infile.open(file.data()); // 将文件流对象与文件连接起来
assert(infile.is_open()); // 若失败,则输出错误消息,并终止程序运行
while (!infile.eof())
{
char c = 65;
infile >> c; // 类似cin>>。 c = infile.get();和 infile.get(c);效果一模一样!该读取要在infile.fail()函数的前面。
if (infile.fail()) { // 如果是文件尾,则if (infile.eof()) { 也行。if (infile.fail()) break;也行
cout << "\n文件读取错误或到达文件尾" << endl;
exit(1);
}
cout << c << endl;
}
infile.close(); // 关闭文件输入流
}
int main()
{
readtxt("1.txt");
return 0;
}
输出:
注: 在几乎所有情况下,若设置 eofbit ,则一同设置 failbit 。 到达文件尾,infile.eof()和infile.fail()都为真。
补充:
人们经常误认为 EOF 是从文件中读取的一个字符。其实,EOF 不是一个字符,它被定义为是 int 类型的一个负数(-1)。EOF 也不是文件中实际存在的内容。EOF 也不是只表示读文件到了结尾这一状态(这种状态可以用 feof() 来检测),它还能表示 I/O 操作中的读、写错误(通常可以用 ferror() 来检测)以及其它一些关联操作的错误状态。
很多人说循环条件EOF只能用于处理文本文件,其实不然,这点不是特别的准确,还要看定义的变量的类型。
下面这段程序对文本文件和二进制文件都可以:
int c;
while((c=fgetc(fp)) != EOF)
{
printf("%X/n", c);
}
如果读到了FF,由于c定义为int型,所以实际上c=0x000000FF,不等于EOF(-1=0xFFFFFFFF),因此不会误判为文件结尾。
但是如果把c定义为char类型,就有可能产生混淆了。
char c;
while((c=fgetc(fp)) != EOF)
{
printf("%X/n", c);
}
因为文本文件中存储的是ASCII码,而ASCII码中FF一般不使用,所以如果读文件返回了FF,说明已经到了文本文件的结尾。但是如果是二进制文件,其中可能会包含FF,因此不能把读到EOF作为文件结束的条件,此时只能用feof()函数。
循环条件!feof(fp)可用于处理文本文件和二进制文件,如果运行如下程序:
char c;
while(!feof(fp)) // while(c != EOF)同理
{
c = fgetc(fp);
printf("%d/n", c);
}
会发现最后输出了一个-1,原因就是在读完最后一个字符后,fp->flag仍然没有被置为_IOEOF,因而feof()仍然没有探测到文件结尾。直到再次调用fgetc()执行读操作,feof()才能探测到文件结尾。这样就多输出了一个-1(即EOF)。
可修改如下:
char c;
c = fgetc(fp);
while(!feof(fp)) // 或 while(c != EOF)
{
printf("%d/n", c);
c = fgetc(fp);
}