Chinaunix

标题: (修正版,欢迎指正)【原创】getchar()和EOF总结 [打印本页]

作者: Godbach    时间: 2007-08-24 15:03
标题: (修正版,欢迎指正)【原创】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
欢迎交流和指正。


[ 本帖最后由 Godbach 于 2007-8-24 17:39 编辑 ]
作者: choc    时间: 2007-08-24 15:12
提示: 作者被禁止或删除 内容自动屏蔽
作者: Godbach    时间: 2007-08-24 15:21
原帖由 choc 于 2007-8-24 15:12 发表
这个好像和K&R里面说的一样啊!


我觉得上面在getchar部分,K&R里面提到了一些,但是也只是简单的提了一下。
对于第二部分,应该在K&R里面提到的不多吧。
:wink: :wink:
作者: ivhb    时间: 2007-08-24 15:39
关于按下CTRL+D部分(EOF总结部分),应该归于终端设置吧。比如,对于当前输入的buffer里
非换行字符,则需要按下两次。第一次,是刷新,第二次是设置结束标志。这个应该说,更多就是一个终端属性设定。试想,你要是stty raw模式,不可能出现这个结论。
作者: Godbach    时间: 2007-08-24 15:49
原帖由 ivhb 于 2007-8-24 15:39 发表
关于按下CTRL+D部分(EOF总结部分),应该归于终端设置吧。比如,对于当前输入的buffer里
非换行字符,则需要按下两次。第一次,是刷新,第二次是设置结束标志。这个应该说,更多就是一个终端属性设定。试想, ...


多谢ivhb兄提醒,当时也就是在终端下做的。有空了,在console下也试下。
作者: ivhb    时间: 2007-08-24 15:52
这个也不是我的总结,是APUE关于终端说明部分的。
看的时间很长了,可能会有偏差。待你实验完,应该可以完善你的这个帖子了。
作者: NewCore    时间: 2007-08-24 16:14
char c;
c = getchar();

char可以为负值,这样写是没有问题的。
作者: ypxing    时间: 2007-08-24 16:24
看看这个链接

http://www.c-faq.com/stdio/getcharc.html


原帖由 NewCore 于 2007-8-24 16:14 发表
char c;
c = getchar();

char可以为负值,这样写是没有问题的。

作者: 思一克    时间: 2007-08-24 16:30
是tty buffer的作用。
getchar()还是按字节读的。也就是说你输入ABCDEFG\n
那么getchar()一共被调用了8次,putchar()也是8次。
作者: ivhb    时间: 2007-08-24 16:31
原帖由 NewCore 于 2007-8-24 16:14 发表
char c;
c = getchar();

char可以为负值,这样写是没有问题的。

怎么会没有问题?
看来你没有遇到过这样的文件而已。
这样

int
main(void)
{
   printf("%cxxxx\n", 255);
}
./a.out > bx


#include <stdio.h>

int
main(void)
{
  char c = getchar();
  if (c == EOF) {
    printf("reach EOF\n");
    if (!feof(stdin))
      printf("not reach EOF\n");
  }
}

./a.out <bx
作者: 思一克    时间: 2007-08-24 16:32
getchar()的返回应定义为INT. 而不是char
作者: Godbach    时间: 2007-08-24 16:42
原帖由 思一克 于 2007-8-24 16:32 发表
getchar()的返回应定义为INT. 而不是char


看高手过招,学习了。

版主说得对,getchar的定义是:int getchar(void), 应该返回int.
作者: NewCore    时间: 2007-08-24 17:01
标题: 有理
有理
作者: Godbach    时间: 2007-08-24 17:04
原帖由 思一克 于 2007-8-24 16:30 发表
是tty buffer的作用。
getchar()还是按字节读的。也就是说你输入ABCDEFG\n
那么getchar()一共被调用了8次,putchar()也是8次。


想起来了,再看C专家编程的时候,作者曾经在第8章中8.6节提到,如何不需要按回车键就能得到一个字符中,曾给出了一个例程,就是如何实现用getchar一次得到一个字符。
马上修改一下帖子,呵呵。
作者: yecheng_110    时间: 2007-08-24 17:18
看看这个帖子
http://bbs.chinaunix.net/thread-233220-1-1.html
作者: Godbach    时间: 2007-08-24 17:36
原帖由 ivhb 于 2007-8-24 15:39 发表
关于按下CTRL+D部分(EOF总结部分),应该归于终端设置吧。比如,对于当前输入的buffer里
非换行字符,则需要按下两次。第一次,是刷新,第二次是设置结束标志。这个应该说,更多就是一个终端属性设定。试想, ...


已经进行了修正!:wink: :wink:
作者: 思一克    时间: 2007-08-24 17:45
鼓励
作者: Godbach    时间: 2007-08-24 17:48
原帖由 思一克 于 2007-8-24 17:45 发表
鼓励


呵呵,多谢版主。
很多问题的发现和解决都得益于各位热心的网友。
作者: stormgenius    时间: 2007-08-24 19:54
长期忽略了这些细节,今天感觉受益匪浅啊
作者: Godbach    时间: 2007-08-25 18:12
原帖由 stormgenius 于 2007-8-24 19:54 发表
长期忽略了这些细节,今天感觉受益匪浅啊


是阿,很多方面的知识如果深究起来的话,才会发现自己的理解还是不够全面。有了交流,提高的就很快。
作者: 醉卧水云间    时间: 2007-08-25 18:31
好多年都没用过getchar了,GUI下用处不大。
作者: Godbach    时间: 2007-08-27 10:03
原帖由 醉卧水云间 于 2007-8-25 18:31 发表
好多年都没用过getchar了,GUI下用处不大。


呵呵,是啊。
BTW,楼上的GUI用什么环境开发啊?
作者: herolf    时间: 2007-08-27 17:09
呵呵,文章写的挺好的啊。支持一下。哥们这种钻研的精神令人佩服啊。
作者: Godbach    时间: 2007-08-27 21:12
呵呵,原来是herolf兄啊。:wink: :wink:
作者: wolfkin    时间: 2007-08-27 21:20
现在基本上不用交互了,都用命令行选项搞定。
作者: 比克流    时间: 2007-10-20 19:47
看高手们讨论,受益菲浅,太感谢了
作者: slackware12    时间: 2009-11-20 17:11
CTRL + D 产生的字符是 EOT(end of transmission), 不是EOF 。 EOT 的ASCII 码为 4.

----------------代码的分隔线------------------------------
int Ch = 0;
while ( (Ch = getchar()) != EOF )
    fprintf(stdout, "%d ", Ch);
--------------------代码的分隔线--------------------------

在vim 中的输入模式, 使用 CTRL + V 和 CTRL +D , 即可输入 EOT 字符。将含EOT字符的文本保存为test文件。

然后 用上面的代码编译产生的可执行程序, 并执行命令 : ./main < test
就可以打印出test文本中的EOT 的 ASCII码.

如偶说的不对, 请海涵~~~
作者: slackware12    时间: 2009-11-20 18:27
下面的家伙说得不错~~~

摘抄自 :  http://www.cnitblog.com/guopingleee/archive/2009/01/29/54047.html

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


    为什么在VC6.0下连续按两次CTrl + Z不会自动退出呢?
    并且我觉得如果输入几个字符后再按一次Ctrl + Z相当于\n但是又不等价于\n
比如我这个程序
#include<stdio.h>
main()
{
int n_space,n_tab,n_enter;
int c;
n_space = n_tab = n_enter = 0;
while( (c = getchar()) != EOF)
if(c == ' ') ++ n_space;
else if(c == '\t') ++ n_tab;
else if(c == '\n') ++ n_enter;
printf("%d,%d,%d",n_space,n_tab,n_enter);
return 0;
}
运行后,如果我输入h e l,然后输入\t,然后输入一次Ctrl + Z(输两次或者更多次也是一样),都不会结束,此时再输入一次回车后,换到下一行的行首,又能够继续输入,如果这一次我输入Ctrl +Z的话,程序才会停止,并且我输入的那一次回车没有被算进去。

感觉有点混乱的感觉。。。




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2