C语⾔三个结束符:EOF‘0’n
>> 关于⽂件结束符EOF
EOF 是 End Of File 的缩写。
在中,它是在标准库中定义的⼀个宏。
⼈们经常误认为 EOF 是从⽂件中读取的⼀个字符(牢记)。其实,EOF 不是⼀个字符,它被定义为是 int 类型的⼀个负数(⽐如 -1)。EOF 也不是⽂件中实际存在的内容。EOF 也不是只表⽰读⽂件到了结尾这⼀状态(这种状态可以⽤ feof() 来检测),它还能表⽰ I/O 操作中的读、写错误(通常可以⽤ ferror() 来检测)以及其它⼀些关联操作的错误状态。
⼀、getchar的两点总结:
当⽤getchar进⾏输⼊时,如果输⼊的第⼀个字符为有效字符(即输⼊不是⽂件结束符EOF,Windows下为组合键Ctrl+Z,Unix/下为组合键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的原因。
这⾥要强调的⼀点就是,getchar函数通常返回终端所输⼊的字符,这些字符系统中对应的ASCII值都是⾮负的。因此,很多时候,我们会写这样的两⾏代码:
char c;
c =getchar();
这样就很有可能出现问题。因为getchar函数除了返回终端输⼊的字符外,在遇到Ctrl+D(下)即⽂件结束符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:
*/
#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.
printf输出格式补0
如果下⼀个读取的字符将为 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转换为整型值。
写个⼩程序验证了⼀下
[cpp]
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型,读取⽂件等时还是能正常结束。