zoukankan      html  css  js  c++  java
  • 文件结束符EOF

    >> 关于文件结束符EOF
    EOF 是 End Of File 的缩写。

    在C语言中,它是在标准库中定义的一个宏。

    人们经常误认为 EOF 是从文件中读取的一个字符(牢记)。其实,EOF 不是一个字符,它被定义为是 int 类型的一个负数(比如 -1)。EOF 也不是文件中实际存在的内容。EOF 也不是只表示读文件到了结尾这一状态(这种状态可以用 feof() 来检测),它还能表示 I/O 操作中的读、写错误(通常可以用 ferror() 来检测)以及其它一些关联操作的错误状态。

    一、getchar的两点总结:
    1.getchar是以行为单位进行存取的。
    当用getchar进行输入时,如果输入的第一个字符为有效字符(即输入不是文件结束符EOF,Windows下为组合键Ctrl+Z,Unix/Linux下为组合键Ctrl+D),那么只有当最后一个输入字符为换行符'/n'(也可以是文件结束符EOF,EOF将在后面讨论)时,getchar才会停止执行,整个程序将会往下执行。譬如下面程序段:

    while((c =getchar())!=EOF){
        putchar(c);
    }


    执行程序,输入:abc,然后回车。则程序就会去执行puchar(c),然后输出abc,这个地方不要忘了,系统输出的还有一个回车。然后可以继续输入,再次遇到换行符的时候,程序又会把那一行的输入的字符输出在终端上。

    对于getchar,肯定很多初学的朋友会问,getchar不是以字符为单位读取的吗?那么,既然我输入了第一个字符a,肯定满足while循环(c = getchar()) != EOF的条件阿,那么应该执行putchar(c)在终端输出一个字符a。不错,我在用getchar的时候也是一直这么想的,但是程序就偏偏不着样执行,而是必需读到一个换行符或者文件结束符EOF才进行一次输出。对这个问题的一个解释是,在大师编写C的时候,当时并没有所谓终端输入的概念,所有的输入实际上都是按照文件进行读取的,文件中一般都是以行为单位的。因此,只有遇到换行符,那么程序会认为输入结束,然后采取执行程序的其他部分。同时,输入是按照文件的方式存取的,那么要结束一个文件的输入就需用到EOF(Enf Of File). 这也就是为什么getchar结束输入退出时要用EOF的原因。
    2.getchar()的返回值一般情况下是字符,但也可能是负值,即返回EOF。

    这里要强调的一点就是,getchar函数通常返回终端所输入的字符,这些字符系统中对应的ASCII值都是非负的。因此,很多时候,我们会写这样的两行代码:

    char c;
    c =getchar();



    这样就很有可能出现问题。因为getchar函数除了返回终端输入的字符外,在遇到Ctrl+D(Linux下)即文件结束符EOF时,getchar()的返回EOF,这个EOF在函数库里一般定义为-1。因此,在这种情况下,getchar函数返回一个负值,把一个负值赋给一个char型的变量是不正确的。为了能够让所定义的变量能够包含getchar函数返回的所有可能的值,正确的定义方法如下(K&R C中特别提到了这个问题):

    int c;
    c =getchar();

    二、EOF的两点总结(主要指普通终端中的EOF)
    1.EOF作为文件结束符时的情况:

    EOF虽然是文件结束符,但并不是在任何情况下输入Ctrl+D(Windows下Ctrl+Z)都能够实现文件结束的功能,只有在下列的条件下,才作为文件结束符。
    (1)遇到getcahr函数执行时,要输入第一个字符时就直接输入Ctrl+D,就可以跳出getchar(),去执行程序的其他部分;
    (2)在前面输入的字符为换行符时,接着输入Ctrl+D;
    (3)在前面有字符输入且不为换行符时,要连着输入两次Ctrl+D,这时第二次输入的Ctrl+D起到文件结束符的功能,至于第一次的Ctrl+D的作用将在下面介绍。
    其实,这三种情况都可以总结为只有在getchar()提示新的一次输入时,直接输入Ctrl+D才相当于文件结束符。

    2.EOF作为行结束符时的情况,这时候输入Ctrl+D并不能结束getchar(),而只能引发getchar()提示下一轮的输入。

    这种情况主要是在进行getchar()新的一行输入时,当输入了若干字符(不能包含换行符)之后,直接输入Ctrl+D,此时的Ctrl+D并不是文件结束符,而只是相当于换行符的功能,即结束当前的输入。以上面的代码段为例,如果执行时输入abc,然后Ctrl+D,程序输出结果为:
    abcabc

    注意:第一组abc为从终端输入的,然后输入Ctrl+D,就输出第二组abc,同时光标停在第二组字符的c后面,然后可以进行新一次的输入。这时如果再次输入Ctrl+D,则起到了文件结束符的作用,结束getchar()。
    如果输入abc之后,然后回车,输入换行符的话,则终端显示为:
    abc         //第一行,带回车
    abc         //第二行
                   //第三行

    其中第一行为终端输入,第二行为终端输出,光标停在了第三行处,等待新一次的终端输入。
    从这里也可以看出Ctrl+D和换行符分别作为行结束符时,输出的不同结果。
    EOF的作用也可以总结为:当终端有字符输入时,Ctrl+D产生的EOF相当于结束本行的输入,将引起getchar()新一轮的输入;当终端没有字符输入或者可以说当getchar()读取新的一次输入时,输入Ctrl+D,此时产生的EOF相当于文件结束符,程序将结束getchar()的执行。

     

    【补充】本文第二部分中关于EOF的总结部分,适用于终端驱动处于一次一行的模式下。也就是虽然getchar()和putchar()确实是按照每次一个字符 进行的。但是终端驱动处于一次一行的模式,它的输入只有到“/n”或者EOF时才结束,因此,终端上得到的输出也都是按行的。
    如果要实现终端在读一个字符就结束输入的话,下面的程序是一种实现的方法(参考《C专家编程》,略有改动)

    /*Edit by Godbach
      CU Blog: http://blog.chinaunix.net/u/33048/
    */
    #include<stdio.h>
    #include<stdlib.h>

    int
    main(void)
    {
        int c;
        /* 终端驱动处于普通的一次一行模式 */
        system("stty raw");
        
        
    /* 现在的终端驱动处于一次一个字符模式 */
        c =getchar();
        putchar();
        
        /* 
    终端驱动处又回到一次一行模式 */
         system("stty cooked");
        
        return 0;
    }

    编译运行该程序,则当如入一个字符时,直接出处一个字符,然后程序结束。
    由此可见,由于终端驱动的模式不同,造成了getchar()输入结束的条件不一样。普通模式下需要回车或者EOF,而在一次一个字符的模式下,则输入一个字符之后就结束了。


    (1) 字节的读取
    在正常的情况下, getc 以 unsigned char 的方式读取文件流, 扩张为一个整数,并返
    回. 换言之, getc 从文件流中取一个字节, 并加上24个零,成为一个小于256的整数,
    然后返回.

    int c;
    while ((c = fgetc (rfp))!= -1) // -1就是 EOF
    fputc (c, wfp);

    上面 fputc 中的 c 虽然是整数, 但在 fputc 将其写入文件流之前, 又把整数的高24位
    去掉了, 因此 fgetc, putc 配合能够实现文件复制. 到目前为止, 把 c 定义为
    char仍然是可行的, 但下面我们将看到,把 c 定义为 int 是为正确判段文件是否结束.

    (2) 判断文件结束.

    多数人认为文件中有一个EOF,用于表示文件的结尾. 但这个观点实际上是错误的,在文
    件所包含的数据中,并没有什么文件结束符. 对getc 而言, 如果不能从文件中读取,
    则返回一个整数 -1,这就是所谓的EOF. 返回 EOF 无非是出现了两种情况,一是文件已
    经读完; 二是文件读取出错,反正是读不下去了.

    请注意: 在正常读取的情况下, 返回的整数均小于256, 即0x0~0xFF. 而读不出返回的
    是 0xFFFFFFFF. 但, 假如你用fputc把 0xFFFFFFFF 往文件里头写, 高24位被屏蔽,写入的将
    是 0xFF. // lixforalpha 请注意这一点

    (3) 0xFF 会使我们混淆吗?

    不会, 前提是, 接收返回值的 c 要按原型定义为 int.

    如果下一个读取的字符将为 0xFF, 则

    int c;
    c = fgetc (rfp); // c = 0x000000FF;
    if (c != -1)    // 当然不等, -1 是 0xFFFFFFFF
    fputc (wfp);   // 噢, OXFF 复制成功.

    字符0xFF, 其本身并不是EOF.

    (4) 将 c 定义 char

    假定下一个读取的字符为 0xFF 则

    char c;
    c = fgetc (rfp); // fgetc(rfp)的值为 0x000000FF, 暗中降为字节, c = 0xFF
    if (c != -1)    // 字符与整数比较? c 被带符号(signed)扩展为0xFFFFFFFF, 喔噢,
    条件成立,文件复制提前退出. 

    while ((c=fgetc(rfp))!=EOF) 中的判别条件成立, 文件复制结束! 意外中止.

    (5) 将 c 定义为 unsigned char;

    当读到文件末尾, 返回 EOF 也就是 -1 时,

    unsigned char c;
    c = fgetc (rfp); // fgetc (rfp)的值为EOF,即-1,即0xFFFFFFFF, 降格为字节, c=0xFF
    if ( c!= -1)  // c 被扩展为 0x000000FF, 永远不回等于 0xFFFFFFFF

    所以这次虽然能正确复制 0xFF, 但却不能判断文件结束. 事实上,在 c 为 uchar 时,
    c != -1 是永远成立的, 一个高质量的编译器, 比如 gcc会在编译时指出这一点.

    (6) 为何需要feof?
    FILE *fp; 
    fp 指向一个很复杂的数据结构, feof 是通过这个结构中的标志来判断文件是否结束的.
    如果文件用 fgetc 读取, 刚好把最后一个字符读出时, fp 中的EOF标志不会打开,这时
    用feof判断,将会得到文件尚未结束的结论.

    fgetc 返回 -1 时, 我们仍无法确信文件已经结束, 因为可能是读取错误! 这时我们
    需要 feof 和 ferror.

     

     

    总结:EOF并不是存在于文件中的,而是一种状态,当读到文件末尾或者读取出错时就会返回这个值来判断文件结束。(即即使读取错误可能也被认为文件结束,所以就需要用feof 和 ferror来判断是不是真的文件结束了)

    当用getchar(c)时,即使c定义成字符型,也可以结束,主要是c与-1比较时,c也会从char转换为整型值。

    写个小程序验证了一下

    1. #include <stdio.h>  
    2. int main()  
    3. {  
    4.   char c;  
    5.   c = -1;  
    6.   printf("%x",c);  
    7.   return 0;  
    8. }  

    得到的结果为ffffffff,所以c即使定义为char型,读取文件等时还是能正常结束。


    关于EOF(文件结束符)问题的体会


    最近写了些代码,在对文件的操作中发现了很经典的EOF问题,呵呵。
    EOF,即end of file,文件结尾,作为文件结束的标志,在程序中常作为判断的一个标志。但在我们平常的程序中却常发生意想不到的结果。
    下面这段程序,猜猜它输出的是什么?
    char c;
    ifstream fin("d://dat");//设d:/dat文件已存在,内容为ab。
    while(!fin.eof())
    {
        fin >> c;
        cout << c;
    }
    输出结果是abb,没想到吗?你可能会问,再输出第一个b的时候,文件指针已经指向了EOF,为何不结束?
    问题的关键是文件EOF机制是怎样运作的。

    我们来谈三个问题:

    1、文件指针
    当打开一个文件时,文件指针位置为0,并不是指向第一个字符,即第一个字符的位置为1。这一点我们可以通过peek()函数验证。peek()返回的是当前文件指针下一个位置的字符。所以有:
    ofstream fo("d://dat");
    fo << 'h';
    fo.close();
    ifstream fi("d://dat");
    char temp = fi.peek();
    cout << temp;
    会显示h。
    还有,用fo.seekp(0,ios::beg),得文件指针为0;fo.seekp(0,ios::end),得文件指针指向最后一个字符。

    2、关于EOF
    很多朋友认为文件尾有EOF,这是错误的。EOF是流的状态标志。在 C++中,是在读取文件失败时才产生EOF。所以第一个程序中,在输出第一个b时,产生了EOF,再输出第二个b时读取到EOF,循环结束。

    3、解决EOF困惑的办法
    我感觉在判断文件结束上,最好的方法就是判断文件指针相对于开头的位置,是否等于文件长度。即:
    long filelen;
    ifstream fin("d://dat");//设d:/dat文件已存在,内容为ab。
    fin.seekg(0,ios::end);
    filelen = fin.tellg();//获取文件长度
    fin.seekg(0,ios::beg);
    while (1)
    {
        if (filelen == fin.tellg())//到达文件尾,即指向EOF
        {
            flag = true;
            break;
        }
        读取数据...
    }

    当然还有别的方法,就是用peek()的预读性。
    peek()返回当前文件指针下一个位置的字符,而指针位置不变。所以我们可以这样:
    while (fi.peel()!=EOF)
    {
        ...
    }
    当while循环体中,文件指针指向最后一个字符,若没有fi.peel()!=EOF,则需要再下一个循环中才能触发EOF。而加了fi.peel()!=EOF后,用预读的方法检测出了EOF。呵呵,这个方法挺好的吧!




      

    ifstream 流 判断文件是否结尾的函数eof()

    分类: C++ 1807人阅读 评论(2) 收藏 举报

      

     

       fstream流的eof()  判断有点不合常理

     

     

       按正常逻辑来说,如果到了文件末尾的话 ,那eof()应返回真

     

       但是,c++输入输出流如何知道是否到末尾呢?

     

       原来是根据的是: 如果fin>>不能再读入数据了,才发现到了文件结尾,这时才给流设定文件结尾的标志,此后调用eof()时,才返回真。

     

     

       假设

     

       fin>>x; //此时文件刚好读完最后一个数据(将其保存在x中)

     

       但是, 这时 fin.eof()仍未假 因为,fin流的标志eofbit是FALSE, fin流此时认为文件还没有到末尾

     

       只有当流再次读写时

     

       fin>>x; 发现已无可读写数据,此时流才知道到达了结尾,这时才将标志eofbit修改为TRUE

     

       此时流才知道了文件到底了末尾

     

     

     

       也就是说,eof在读取完最后一个数据后,仍是False,

     

                      当再次试图读一个数据时,由于发现没数据可读了 才知道到末尾了,此时才修改标志,eof变为TRUE

     

     

     

     

     

      以下例子:

     

     

     ifstream fin("D://line.txt");

     ofstream fout("D://T_line.txt",ios::trunc);


     list<tag_Point> test_list;

     tag_Point test; 

     

     while (!fin.eof())
     {
      


      fin>>test.x;
      fin>>test.y;
      fin>>test.z;


     

      test_list.push_back(test);
      

     }

       fin.close();

      

     在运行时 发现  test_list中的数据比文本中的数据多一行,也就是 文本中最后一行的数据写了两遍

     

     始终无法理解

     

      现在明白了:》

     

      再读完最后一行后,

     

      因为fin.eof()仍为假, 所以会继续while循环

     

      当执行到while的第一个语句   fin>>test.x时,发现无可读数据了,此时修改流属性,fin.eof ()变为TRUE

     

      再执行   fin>>test.y; fin>>test.z;时,因为已经到文件末尾了 ,所以 test保留了上次的值,也即test中的值为变,还是文本最后一行

     

    的数据

     

      此时再push_back(test),压入列表的仍是最后一行数据

     

      由此导致了,列表中的数据比文本中的数据多一行

     

     

     

    ---------------------

     

     

    知道了原因 ,便很好作出修改了

     

     

      修改为:

     

     while (  fin>>test.x&&fin>>test.y&& fin>>test.z)
     {
      

      test_list.push_back(test);
      

     }

       fin.close();

     

     

     这样便没问题了 ,当读取完最后一行数据后,将其放入列表中,此时判断while条件,也就是再次读取数据,发现无数据可读,读取不成功 fin>>test.x返回False 由此结束循环。

     

     

     

    C++编程语言中的很多功能在我们的实际应用中起着非常大的作用。比如在对文件文本的操作上,就可以用多种方式来实现。在这里我们介绍的C++ eof()函数就是其中一个比较常用的基本函数。

    在使用C/C++读文件的时候,一定都使用过C++ eof()函数来判断文件是否为空或者是否读到文件结尾了,也会在使用这个函数的过程中遇到一些问题,如不能准确的判断是否为空或者是否到了文件尾,以至于有些人可能还会怀疑这个函数是不是本身在设计上就有问题。

    先来看看如下这段代码:

    1. #include < iostream>   
    2. #include < fstream>   
    3. using namespace std;   
    4. int main()   
    5. {   
    6. char ch = 'x';   
    7. ifstream fin("test.txt" /*, ios::binary*/);   
    8. if (fin.eof())   
    9. {   
    10. cout < <  "file is empty."< < endl;   
    11. return 0;   
    12. }   
    13. while (!fin.eof())   
    14. {   
    15. fin.get(ch);   
    16. cout < <  ch;   
    17. }   
    18. system("pause");   
    19. return 0;   
    20. 如果test.txt不存在,程序会形成死循环,fin.eof()永远返回false,就是说,eof在读取完最后一个数据后,仍是False,当再次试图读一个数据时,由于发现没数据可读了 才知道到末尾了,此时才修改标志,eof变为TRUE 

        如果test.txt为空,程序打印出一个x字符,因为循环刚进来时eof()状态还没设置,当读不到数据时设置为ture循环结束;

      当test.txt中存在一字符串“abcd”且没有换行时,程序打印出“abcdd”,

      当存在以上字符串并且有一新的空行时,程序打印出“abcd”加上一空行。其实是两行 oA输出了两次,显示调试器的caret在第三行,为什么没OD了因为是以文本方式打开的 odoa自动转化为oa;

      这种现象可能让很多人很迷惑,程序运行的结果似乎很不稳定,时对时错。使用binary模式读时结果一样。在这里,大家可能有一个误区,认为eof()返回true时是读到文件的最后一个字符,其实不然,C++ eof()函数返回true时是读到文件结束符0xFF,而文件结束符是最后一个字符的下一个字符。

       while(infile.peek()!=EOF)  好处在于他获取的下一个数据(但并不会让FILE指针++哦)
  • 相关阅读:
    118/119. Pascal's Triangle/II
    160. Intersection of Two Linked Lists
    168. Excel Sheet Column Title
    167. Two Sum II
    172. Factorial Trailing Zeroes
    169. Majority Element
    189. Rotate Array
    202. Happy Number
    204. Count Primes
    MVC之Model元数据
  • 原文地址:https://www.cnblogs.com/lgh1992314/p/5835184.html
Copyright © 2011-2022 走看看