关于文件结束符EOF

EOF 是 End Of File 的缩写。

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

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

getchar 返回EOF如果读到文件末

大师级经典的著作,要字斟句酌的去读,去理解。以前在看K&R的The C Programming Language(SecondEdition)
第1.5节的字符输入/输出,被getchar()和EOF所迷惑了。可能主要还是由于没有搞清楚getchar()的工作原理和EOF的用法。因此,感觉很有必要总结一下,不然,很多琐碎的知识点长时间过后就会淡忘的,只有写下来才是最好的方法。

其实,getchar()最典型的程序也就几行代码而已。本人所用的环境是DebianGNU/Linux,在其他系统下也一样。
一、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,而在一次一个字符的模式下,则输入一个字符之后就结束了。

希望本文可以对初学C的朋友提供一点帮助,也希望能和其他朋友进行交流。其中理解不对的地方若能得到指正和建议,本人将不胜感激。同时,本文参考了chinaunix.net关于getchar讨论的帖子和一位博友的文章,链接地址分别为:
http://blog.chinaunix.net/u/9861/showart_64652.html
http://bbs.chinaunix.net/viewthread.php?tid=679688&extra=&page=1
欢迎交流和指正。

在另一个贴子中,我与一些朋友对 getc 展开了一些讨论. 由于觉得楼主最终未能明白
我的意思,所以我把我个人的看法总结出来,写在这里.我不太擅长说明,但已经尽力了. 
任何人转本贴, 请务必把本人的名字写在显眼的位置.  

约定编译器为 gcc2/x86: 
所以 char, unsigned char 为 8 位, int 为 32 位

请参考 http://bbs.chinaunix.net/forum/23/20031223/229236.html

(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.

原文地址:https://www.cnblogs.com/jiangzhaowei/p/8971294.html

时间: 2024-10-25 15:27:26

关于文件结束符EOF的相关文章

文件结束符的使用

问题描述:windows下 int main() { string str; while(cin>>str); cout<<"\n"<<"end"<<endl; } 控制台输入多个字符串后,输入文件结束符crtl-Z,并未使while循环结束,该如何跳出while循环? 解决方法: 方法1:回车 - ctrl-z - 回车 方法2:改代码,设置自己的文件结束符String a;while((cin>> a)

[Jenkins]运行shell报错:寻找匹配的 `&quot;&#39; 是遇到了未预期的文件结束符

这里有一个坑(至少对于我来说): 报错信息中的出错行,并不代表真实脚本中的出错行. jenkins执行的shell内容如下: 第1行echo 1个字符串,少1个双隐号 echo "233342 echo "12312" echo "34" 执行jenkins报错如下: /usr/local/tomcat/apache-tomcat-8.5.40/temp/jenkins5082891078041588552.sh:行4: 寻找匹配的 `"' 是遇

文件结束符

Linux: CTRL + d Windows: CTRL + z

小何讲Linux: 基本文件操作和实例

文件操作的基本概念参见博客: 小何讲Linux: 底层文件I/O操作 1.  函数说明 open()函数:是用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等各种参数. 所谓打开文件实质上是在进程与文件之间建立起一种连接,而"文件描述符"唯一地标识着这样一个连接 close()函数:是用于关闭一个被打开的文件.当一个进程终止时,所有被它打开的文件都由内核自动关闭,很多程序都使用这一功能而不显示地关闭一个文件. read()函数:是用于将从指定的文件描述符中读出的数据

linux文件空洞

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> int main(int argc, char const *argv[]) { char *buf[1024]; int recfile=

Python文件操作汇总

python2.7 1. 文件的打开(内建函数)  open(file_path,mode=‘r’,buffering=-1) <1> file_path是必须给出的参数,是要读取文件的绝对或者相对路径,要包含文件后缀. 绝对路径的3种表示方法:>>> file_path = "C:/tmp/123.txt"       >>> file_path = "C:\\tmp\\123.txt"   >>>

Linux C 字符函数 getchar()、putchar() 与 EOF 详解

首先给出<The_C_Programming_Language>这本书中的例子: #include <stdio.h> int main() { int c; c = getchar(); while (c != EOF) { putchar(); c = getchar(); } return 0; } 这里主要解释下为什么要用int型来接受getchar函数. 很多时候,我们会写这样的两行代码: char c; c = getchar(); 这样就很有可能出现问题.因为getc

文件的结尾和文件开头

c语言中文件的结尾指的是文件的最后一个字符的下一个字符 例如:文件a.txt中有三个字符abc,即文件大小为3 那么文件的实际内容如下图. echo -n abc > a.txt #include <stdio.h> #include <stdlib.h> int main(void){     FILE* fp = fopen("a.txt","r");     if(NULL==fp){         perror("f

同时实现两个文件的内容

字面上很容易理解,两个文件的内容在同一个水平画面上.文件1占1-30列,然后中间20列用空格隔开,文件2占剩下的30列. 下面我说下我的思路: 比如说规定两篇文章水平的长度为80,文件1先输入1-30个字符(输够的话就用空格符来顶替),然后在用20个空格副隔开,接着再输入文件2的内容30个字符(输够的话也用空格补充),这样就完成了一行的输入.这么想就简单了,两个循环,先外循环输入一行,再内循环输入80个字符.行数是不确定的,因为不知道文件的内容(就算知道了计算也很麻烦),但是列数是确定的,就80