(修正版,欢迎指正)【原创】getchar()和EOF总结
时间:2008-01-16 11:13:45 来源: 作者:
|
大师级经典的著作,要字斟句酌的去读,去理解。以前在看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才会停止执行,整个程序将会往下执行。譬如下面程序段: [table=95%][tr][td][font=FixedSys][color=#000000][color=#0000ff]while[/color][color=#0000cc]([/color][color=#0000cc]([/color]c [color=#0000cc]=[/color] [color=#ff0000]getchar[/color][color=#0000cc]([/color][color=#0000cc])[/color][color=#0000cc])[/color] [color=#0000cc]![/color][color=#0000cc]=[/color] [color=#ff0000]EOF[/color][color=#0000cc])[/color][color=#0000cc]{[/color] [color=#ff0000]putchar[/color][color=#0000cc]([/color]c[color=#0000cc])[/color][color=#0000cc];[/color] [color=#0000cc]}[/color][/color][/font][/td][/tr][/table] 执行程序,输入: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值都是非负的。因此,很多时候,我们会写这样的两行代码: [table=95%][tr][td][font=FixedSys][color=#000000][color=#0000ff]char[/color] c[color=#0000cc];[/color] c [color=#0000cc]=[/color] [color=#ff0000]getchar[/color][color=#0000cc]([/color][color=#0000cc])[/color][color=#0000cc];[/color][/color][/font][/td][/tr][/table] 这样就很有可能出现问题。因为getchar函数除了返回终端输入的字符外,在遇到Ctrl+D(Linux下)即文件结束符EOF时,getchar()的返回EOF,这个EOF在函数库里一般定义为-1。因此,在这种情况下,getchar函数返回一个负值,把一个负值赋给一个char型的变量是不正确的。为了能够让所定义的变量能够包含getchar函数返回的所有可能的值,正确的定义方法如下(K&R C中特别提到了这个问题): [table=95%][tr][td][font=FixedSys][color=#000000][color=#0000ff]int[/color] c[color=#0000cc];[/color] c [color=#0000cc]=[/color] [color=#ff0000]getchar[/color][color=#0000cc]([/color][color=#0000cc])[/color][color=#0000cc];[/color][/color][/font][/td][/tr][/table] 二、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,程序输出结果为: [color=Red]abcabc[/color] 注意:第一组abc为从终端输入的,然后输入Ctrl+D,就输出第二组abc,同时光标停在第二组字符的c后面,然后可以进行新一次的输入。这时如果再次输入Ctrl+D,则起到了文件结束符的作用,结束getchar()。 如果输入abc之后,然后回车,输入换行符的话,则终端显示为: [color=Red]abc //第一行,带回车[/color] [color=Red]abc //第二行[/color] [color=Red] //第三行[/color] 其中第一行为终端输入,第二行为终端输出,光标停在了第三行处,等待新一次的终端输入。 从这里也可以看出Ctrl+D和换行符分别作为行结束符时,输出的不同结果。 EOF的作用也可以总结为:当终端有字符输入时,Ctrl+D产生的EOF相当于结束本行的输入,将引起getchar()新一轮的输入;当终端没有字符输入或者可以说当getchar()读取新的一次输入时,输入Ctrl+D,此时产生的EOF相当于文件结束符,程序将结束getchar()的执行。 【补充】本文第二部分中关于EOF的总结部分,适用于终端驱动处于一次一行的模式下。也就是虽然getchar()和putchar()确实是按照每次一个字符[font=Arial] 进行的。但是终端驱动处于一次一行的模式,它的输入只有到“n”或者EOF时才结束,因此,终端上得到的输出也都是按行的。 如果要实现终端在读一个字符就结束输入的话,下面的程序是一种实现的方法(参考《C专家编程》,略有改动) [table=95%][tr][td][font=FixedSys][color=#000000][color=#ff9900]/*Edit by Godbach CU Blog: http://blog.chinaunix.net/u/33048/ */[/color] [color=#0000cc]#[/color][color=#ff0000]include[/color] [color=#0000cc]<[/color]stdio[color=#0000cc].[/color]h[color=#0000cc]>[/color] [color=#0000cc]#[/color][color=#ff0000]include[/color] [color=#0000cc]<[/color]stdlib[color=#0000cc].[/color]h[color=#0000cc]>[/color] [color=#0000ff]int[/color] main[color=#0000cc]([/color][color=#0000ff]void[/color][color=#0000cc])[/color] [color=#0000cc]{[/color] [color=#0000ff]int[/color] c[color=#0000cc];[/color] /* 终端驱动处于普通的一次一行模式 */ [color=#ff0000]system[/color][color=#0000cc]([/color][color=#ff00ff]"stty raw"[/color][color=#0000cc])[/color][color=#0000cc];[/color] [/color][/font][font=Arial][font=FixedSys][color=#000000]/* 现在的终端驱动处于一次一个字符模式 */[/color][/font][/font] [font=FixedSys][color=#000000] c [color=#0000cc]=[/color] [color=#ff0000]getchar[/color][color=#0000cc]([/color][color=#0000cc])[/color][color=#0000cc]; [/color][color=#ff0000]putchar[/color][color=#0000cc]([/color][color=#0000cc])[/color][color=#0000cc]; /* [/color][/color][/font][font=Arial][font=FixedSys][color=#000000]终端驱动处又回到一次一行模式 [/color][/font][/font][font=FixedSys][color=#000000][color=#0000cc]*/ [/color] system("stty cooked"); return 0; } [/color][/font][/td][/tr][/table] 编译运行该程序,则当如入一个字符时,直接出处一个字符,然后程序结束。 由此可见,由于终端驱动的模式不同,造成了getchar()输入结束的条件不一样。普通模式下需要回车或者EOF,而在一次一个字符的模式下,则输入一个字符之后就结束了。 希望本文可以对初学C的朋友提供一点帮助,也希望能和其他朋友进行交流。其中理解不对的地方若能得到指正和建议,本人将不胜感激。同时,本文参考了chinaunix.net关于getchar讨论的帖子和一位博友的文章,链接地址分别为:[/font] [url=http://blog.chinaunix.net/u/9861/showart_64652.html]http://blog.chinaunix.net/u/9861/showart_64652.html [url=http://bbs.chinaunix.net/viewthread.php?tid=679688&extra=&page=1]http://bbs.chinaunix.net/viewthread.php?tid=679688&extra=&page=1 [font=Arial]欢迎交流和指正。 [/font] [ 本帖最后由 Godbach 于 2007-8-24 17:39 编辑 ] choc 回复于:2007-08-24 15:12:00 这个好像和K&R里面说的一样啊! Godbach 回复于:2007-08-24 15:21:11 引用:原帖由 choc 于 2007-8-24 15:12 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7258013&ptid=981231]
这个好像和K&R里面说的一样啊! 我觉得上面在getchar部分,K&R里面提到了一些,但是也只是简单的提了一下。 对于第二部分,应该在K&R里面提到的不多吧。 :wink: :wink: ivhb 回复于:2007-08-24 15:39:12 关于按下CTRL+D部分(EOF总结部分),应该归于终端设置吧。比如,对于当前输入的buffer里 非换行字符,则需要按下两次。第一次,是刷新,第二次是设置结束标志。这个应该说,更多就是一个终端属性设定。试想,你要是stty raw模式,不可能出现这个结论。 Godbach 回复于:2007-08-24 15:49:40 引用:原帖由 ivhb 于 2007-8-24 15:39 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7258301&ptid=981231]
关于按下CTRL+D部分(EOF总结部分),应该归于终端设置吧。比如,对于当前输入的buffer里 非换行字符,则需要按下两次。第一次,是刷新,第二次是设置结束标志。这个应该说,更多就是一个终端属性设定。试想, ... 多谢ivhb兄提醒,当时也就是在终端下做的。有空了,在console下也试下。 ivhb 回复于:2007-08-24 15:52:56 这个也不是我的总结,是APUE关于终端说明部分的。 看的时间很长了,可能会有偏差。待你实验完,应该可以完善你的这个帖子了。 NewCore 回复于:2007-08-24 16:14:12 char c; c = getchar(); char可以为负值,这样写是没有问题的。 ypxing 回复于:2007-08-24 16:24:21 看看这个链接 http://www.c-faq.com/stdio/getcharc.html 引用:原帖由 NewCore 于 2007-8-24 16:14 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7258590&ptid=981231]
char c; c = getchar(); char可以为负值,这样写是没有问题的。 思一克 回复于:2007-08-24 16:30:00 是tty buffer的作用。 getchar()还是按字节读的。也就是说你输入ABCDEFGn 那么getchar()一共被调用了8次,putchar()也是8次。 ivhb 回复于:2007-08-24 16:31:23 引用:原帖由 NewCore 于 2007-8-24 16:14 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7258590&ptid=981231]
char c; c = getchar(); char可以为负值,这样写是没有问题的。 怎么会没有问题? 看来你没有遇到过这样的文件而已。 这样 你 int main(void) { printf("%cxxxxn", 255); } ./a.out > bx #include <stdio.h> int main(void) { char c = getchar(); if (c == EOF) { printf("reach EOFn"); if (!feof(stdin)) printf("not reach EOFn"); } } ./a.out <bx 思一克 回复于:2007-08-24 16:32:52 getchar()的返回应定义为INT. 而不是char Godbach 回复于:2007-08-24 16:42:48 引用:原帖由 思一克 于 2007-8-24 16:32 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7258756&ptid=981231]
getchar()的返回应定义为INT. 而不是char 看高手过招,学习了。 版主说得对,getchar的定义是:int getchar(void), 应该返回int.:) NewCore 回复于:2007-08-24 17:01:36 有理 Godbach 回复于:2007-08-24 17:04:36 引用:原帖由 思一克 于 2007-8-24 16:30 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7258736&ptid=981231]
是tty buffer的作用。 getchar()还是按字节读的。也就是说你输入ABCDEFGn 那么getchar()一共被调用了8次,putchar()也是8次。 想起来了,再看C专家编程的时候,作者曾经在第8章中8.6节提到,如何不需要按回车键就能得到一个字符中,曾给出了一个例程,就是如何实现用getchar一次得到一个字符。 马上修改一下帖子,呵呵。 yecheng_110 回复于:2007-08-24 17:18:21 看看这个帖子 http://bbs.chinaunix.net/thread-233220-1-1.html Godbach 回复于:2007-08-24 17:36:47 引用:原帖由 ivhb 于 2007-8-24 15:39 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7258301&ptid=981231]
关于按下CTRL+D部分(EOF总结部分),应该归于终端设置吧。比如,对于当前输入的buffer里 非换行字符,则需要按下两次。第一次,是刷新,第二次是设置结束标志。这个应该说,更多就是一个终端属性设定。试想, ... 已经进行了修正!:wink: :wink: 思一克 回复于:2007-08-24 17:45:40 鼓励 Godbach 回复于:2007-08-24 17:48:08 引用:原帖由 思一克 于 2007-8-24 17:45 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7259383&ptid=981231]
鼓励 呵呵,多谢版主。 很多问题的发现和解决都得益于各位热心的网友。:em02: :em02: stormgenius 回复于:2007-08-24 19:54:16 长期忽略了这些细节,今天感觉受益匪浅啊:em02: Godbach 回复于:2007-08-25 18:12:15 引用:原帖由 stormgenius 于 2007-8-24 19:54 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7259975&ptid=981231]
长期忽略了这些细节,今天感觉受益匪浅啊:em02: 是阿,很多方面的知识如果深究起来的话,才会发现自己的理解还是不够全面。有了交流,提高的就很快。:mrgreen::mrgreen: 醉卧水云间 回复于:2007-08-25 18:31:46 好多年都没用过getchar了,GUI下用处不大。 Godbach 回复于:2007-08-27 10:03:49 引用:原帖由 醉卧水云间 于 2007-8-25 18:31 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7262653&ptid=981231]
好多年都没用过getchar了,GUI下用处不大。 呵呵,是啊。 BTW,楼上的GUI用什么环境开发啊? herolf 回复于:2007-08-27 17:09:01 呵呵,文章写的挺好的啊。支持一下。哥们这种钻研的精神令人佩服啊。 Godbach 回复于:2007-08-27 21:12:58 呵呵,原来是herolf兄啊。:wink: :wink: wolfkin 回复于:2007-08-27 21:20:06 现在基本上不用交互了,都用命令行选项搞定。 比克流 回复于:2007-10-20 19:47:33 看高手们讨论,受益菲浅,太感谢了 |
原文链接:http://bbs.chinaunix.net/viewthread.php?tid=981231 转载请注明作者名及原文出处 |










文章评论
共有 位网友发表了评论 查看完整内容