为什么“while(!feof(file))”总是错的?

使用feof()控制读取循环有什么问题?例如:

#包括<标准h>
#包括<stdlib.h>
int
主(内部argc,字符**argv)
{
字符*路径=“stdin”;;
文件*fp=argc>1?fopen(路径=argv[1],“r"):stdin;
如果(fp==NULL){
佩罗尔(路径);
返回退出失败;
}
而(!feof(fp)){/*这是错误的*/
/*从文件中读取和处理数据*/
}
如果(fclose(fp)!=0){
佩罗尔(路径);
返回退出失败;
}
返回退出成功;
}

这个循环有什么问题

TL;博士

while(!feof)是错误的,因为它测试的是无关的东西,而无法测试您需要知道的东西。结果是,您错误地执行了假定它正在访问已成功读取的数据的代码,而事实上这种情况从未发生过

我想提供一个抽象的、高层次的视角。因此,如果你对(!feof)的实际功能感兴趣,请继续阅读

并发性和同时性

I/O操作与环境交互。环境不是程序的一部分,也不在您的控制之下。“环境确实存在”;同时”;用你的程序。与所有同时发生的事情一样,关于;“当前状态”;没有意义:没有“的概念”;“同时性”;跨并发事件。许多状态属性根本不同时存在

让我更准确地说:假设你想问,“我想问你一个问题。”;你有更多的数据吗;。您可以询问并发容器或I/O系统。但答案通常是不可操作的,因此毫无意义。那么,如果容器上写着;是"——当您尝试读取时,它可能不再有数据。同样,如果答案是;否;,当您尝试读取时,数据可能已经到达。结论是,没有类似于“的属性”;“我有数据”;,因为你不能对任何可能的答案做出有意义的反应。(缓冲输入的情况稍微好一点,你可能会得到一个“是的,我有数据”,这构成了某种保证,但你仍然必须能够处理相反的情况。而输出的情况肯定和我描述的一样糟糕:你永远不知道是磁盘还是网络缓冲区已满。)

因此,我们得出结论,询问I/O系统是否能够执行I/O操作是不可能的,而且事实上是不合理的。我们可以与它交互的唯一可能的方法(就像与并发容器交互一样)是尝试操作,并检查操作是否成功。在您与环境交互的那一刻,然后,也只有那时,您才能知道交互是否实际可行,并且在那一点上,您必须承诺执行交互。(如果您愿意,这是一个“同步点”。)

EOF

现在我们来谈谈EOF。EOF是从尝试的I/O操作中得到的响应。这意味着您试图读取或写入某些内容,但在执行此操作时,您无法读取或写入任何数据,而是遇到了输入或输出的结尾。对于所有I/O API,无论是C标准库、C++ IOFROTS或其他库,这都是正确的。只要I/O操作成功,您就无法知道未来的操作是否会成功。您必须始终首先尝试该操作,然后对成功或失败作出反应

例子

在每个示例中,请仔细注意,我们首先尝试I/O操作,然后如果结果有效,则使用结果。请进一步注意,我们始终必须使用I/O操作的结果,尽管每个示例中的结果具有不同的形状和形式

  • C stdio,从文件中读取:

    (;;)的

    {
    尺寸n=fread(buf,1,bufsize,infle);
    消耗(buf,n);
    如果(n==0){break;}
    }
    

我们必须使用的结果是n,即读取的元素数(可能只有零)

  • C stdio,scanf

    用于(int a、b、c;scanf(“%d%d%d”、“a”、“b”、“c)==3;”){
    消耗(a、b、c);
    }
    

我们必须使用的结果是scanf的返回值,即转换的元素数

  • C++,iostreams格式的提取:

    (int n;std::cin>n;)的

    ){
    消耗(n);
    }
    

我们必须使用的结果是std::cin本身,它可以在布尔上下文中进行计算,并告诉我们流是否仍处于good()状态

  • C++,iostreams getline:

    (std::string line;std::getline(std::cin,line);){
    消费(线);
    }

我们必须再次使用的结果是std::cin,与之前一样

  • POSIX,写入(2)以刷新缓冲区:

    字符常量*p=buf;
    ssize_t n=bufsize;
    对于(ssize_t k=bufsize;(k=write(fd,p,n))>0;p+=k,n-=k){
    如果(n!=0){/*错误,写入完整缓冲区失败*/}
    

我们在这里使用的结果是k,即写入的字节数。这里的要点是,我们只能知道在写入操作之后写入了多少字节

  • POSIXgetline()

    char*buffer=NULL;
    尺寸_tbufsiz=0;
    ssize_t nbytes;
    while((nbytes=getline(&buffer,&bufsiz,fp))!=-1)
    {
    /*在缓冲区中使用N字节的数据*/
    }
    自由(缓冲);
    

    我们必须使用的结果是nbytes,即新行之前(包括新行)的字节数(如果文件未以新行结尾,则为EOF)

    请注意,当发生错误或达到EOF时,函数显式返回-1(而不是EOF!)

您可能会注意到,我们很少拼写出实际的单词"EOF"。我们通常以我们更感兴趣的其他方式检测错误情况(例如,未能执行我们所期望的I/O)。在每个示例中,都有一些API特性可以明确地告诉我们遇到了EOF状态,但实际上这并不是一条非常有用的信息。这是一个比我们通常关心的更多的细节。重要的是I/O是否成功,而不是它如何失败

  • 最后一个实际查询EOF状态的示例:假设您有一个字符串,并且希望测试它是否完整地表示一个整数,除了空格之外,末尾没有额外的位。使用C++ IOFSUBLE,它是这样的:

    std::string input="123“;//实例
    std::istringstream iss(输入);
    int值;
    if(iss>>value>std::ws&iss.get()==EOF){
    消费(价值);
    }否则{
    //错误,“输入”不能作为整数进行分析
    }
    

我们在这里使用两个结果。第一个是iss,流对象本身,用于检查格式化后对值的提取是否成功。但是,在同样使用空白之后,我们执行另一个I/O/操作,iss.get(),并期望它作为EOF失败,如果格式化提取已经使用了整个字符串,则情况就是这样

在C标准库中,您可以通过检查结束指针是否到达输入字符串的末尾,来实现类似于strto*l函数的功能

发表评论