为什么用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);
} 

 

posted @ 2019-06-26 20:51  Adano1  阅读(263)  评论(0编辑  收藏  举报