- 论坛徽章:
- 0
|
关于流和缓冲区的理解以及一般标准输入问题的解决方法小结
三、一般标准输入易产生的问题
stdin和stdout是行缓存,所以在进行标准输入输出时就存在着缓存刷新(flush)的问题。就像仓库一样,如果不进行有效清理,就有可能将已淘汰的东西错误地当作新货发给客户了。stdin和stdout如果不及时刷新的话,就有可能输入或输出错误的数据。
此时,回到本文开头给出的两个例子。第一个例子中在system("pause" ;这条语句的后面加上printf("%d\n", c);这一句,就会发现打印出c的整数值为10,查一下ASCII码10对应的字符是换行符(Line Feed),这样
for (; (c = getchar()) == 'y' || c == 'Y'; )的条件判断为假,所以程序不可能再循环下去。这个换行符LF怎么来的呢?就是我们第一次输入‘y’时按下的回车符。(这里要说明一点是:键盘的回车符在C中被处理成换行符(LF),它与ASCII码中对应的回车符(carriage return ,ASCII为13)是不同的,这样一来依靠键盘一键输入’\r’(carriage return)是不可能了。好在行缓存是以’\n’作为换行标志的)。就是因为标准输入流是行缓存的,所以换行符没有被丢弃反而自动成了下一次的输入了。第二个例子中出错并不是因为有换行符自动做了输入,而是因为某次偶尔的错误输入导致的,程序没了健壮性很大一部分是因为没有考虑到标准输入流具有行缓存特征。
四、此类问题的解决方法小结
既然是有垃圾留在了缓冲区就有治标与治本两大方法了。
1、 治标
a) 清理垃圾。将还驻留在缓存中的无用数据进行清理。
---利用fflush()函数来刷新缓存。但是特别要注意的是fflush()的特点。
(引自ISO/IEC《 ISO/IEC 9899:1999 (E) 》)
Description
If stream points to an output stream or an update stream in which the most recent
operation was not input, the fflush function causes any unwritten data for that stream to be delivered to the host environment to be written to the file; otherwise, the behavior is undefined.
If stream is a null pointer, the fflush function performs this flushing action on all
streams for which the behavior is defined above.
以上突显部分就是说明fflush()用于stdin时是不确定的,它可能返回0以表示刷新成功,但事实上它对stdin没有任何动作。因此用fflush()来刷新stdin引入了很多问题,以下是一些例子:
1)摘自Frequently Asked Questions in comp.lang.c
(http://www.lysator.liu.se/c/c-faq/c-faq-toc.html#c-11)
11.12: How can I flush pending input so that a user's typeahead isn't read at the next prompt? Will fflush(stdin) work?
Answer: fflush is defined only for output streams. Since its definition of "flush" is to complete the writing of buffered characters (not to discard them), discarding unread input would not be an analogous meaning for fflush on input streams. There is no standard way to discard unread characters from a stdio input buffer, nor would such a way be sufficient; unread characters can also accumulate in other, OS-level input buffers.
2)
3)(http://www.mega-nerd.com/erikd/BOOK/#Q5)
Q 5 : In chapter 13, listing 6, fflush (stdin) doesn't work like its supposed to. Why is that?
This is a bug .
The problem with the existing code occurs when a program reads user input, then does some time consuming processing (say more than a couple of seconds) before reading more user input. In this situation, the user might type some more on the keyboard while the program is processing and these characters will be buffered by the operating system and read on the next read call. The idea of the existing code was to use fflush (stdin) to flush all the stored characters from the input buffer.
As Benjamin Black pointed out, the C Programming FAQ states that the behaviour of fflush () is defined only for output streams. To make matters worse there is no standard ANSI/ISO C way of doing this. Fortunately there is a POSIX solution. The POSIX solution is to use the low-level unbuffered input functions or GNU/Linux specific higher level function which in turn use the lower level ones. There are examples of both here.
更详细的解答:http://www.mega-nerd.com/erikd/BOOK/list1306.html
----利用getchar()在下一次输入之前吃掉第一个字符,这里常被用来吃掉一个换行符(例如第一例中)。这里要注意的是:getchar()是一个宏,等同于宏getc()带参数stdin,它不是函数(虽然此宏中由函数)。并且它每次只能读取一个字符,对于垃圾字符数目不定的情况,就无法把握了。
(摘自TC2.0中stdio.h文件)
#define getc(f) \
((--((f)->;level) >;= 0) ? (unsigned char)(++(f)->;curp)[-1] : \
_fgetc (f))
#define putc(c,f) \
((++((f)->;level) < 0) ? (unsigned char)((++(f)->;curp)[-1]=(c)) : \
_fputc ((c),f))
#define getchar() getc(stdin)
#define putchar(c) putc((c), stdout)
---另一个就是gets()函数(这个新手最喜欢,而老手最慎重的函数)。gets()读取与stdin相关联的流中的字符串,直到文件结束或换行符,gets()读取换行符并将其舍去。
(引自ISO/IEC《 ISO/IEC 9899:1999 (E) 》)
Description
The gets function reads characters from the input stream pointed to by stdin, into the array pointed to by s, until end-of-file is encountered or a new-line character is read. Any new-line character is discarded, and a null character is written immediately after the last character read into the array.
一般使用gets()都需要一个临时变量来存放这些读出的缓存区残留数据,所以比较浪费资源,使用它时,为了使程序更具模块性,可以用一个函数来实现:
- void clear_kb(void)
- /* Clears stdin of any waiting characters. */
- }
- char junk[80];
- gets(junk);
- }
复制代码
但是, gets是一个不推荐使用的函数。问题是调用者在使用gets时不能指定缓存的长度。这样就可能造成缓存越界(如若该行长于缓存长度),写到缓存之后的存储空间中,从而产生不可予料的后果。很多时候,还是推荐用fgets()函数,就像
相对strcpy()函数更推荐使用strncpy()一样。
----scanf()函数。由于scanf()函数在读取输入值时将跳过空白符、制表符以及换行符,所以在某种程度上,由它读取值时可以不受遗留在缓存中的换行符影响。但是scanf()函数作为输入函数本身就存在居多的不安全因素(处理字符串时不检查buffer边界),使用它就有可能像搬起了一块大石头,一不小心就会砸到自己的脚。
b) 出污泥而不染
什么叫“出污泥而不染”呢?那就是程序员自己从缓存中挑拣出有用的信息,采用“挑三拣四”的输入函数(例如fgets()、fread()及文件定位函数fseek()、ftell()、rewind()、fgetpos()、fsetpos())来读取换存,使不需要的信息难以被读取。这种方法听起来很好,但做起来难度非同寻常了。
2、 治本
治标的办法可能在小代码内很见效果,也最容易被人想到,但是,一旦问题变复杂了,使用治标的办法来解决无疑是隔靴挠痒,代码不能有很好的健壮性。其实,出现问题也主要是标准输入流的缓存机制造成的,若我们变被动为主动,直接对缓存本身进行相应操作以方便我们使用,那问题不就好解决多了吗?
(以下摘自Stevens的《UNIX环境高级编程》第五章)
对任何一个给定的流,如果我们并不喜欢这些系统默认,则可调用下列两个函数中的一个更改缓存类型:
#include <stdio.h>;
void setbuf(FILEf p*, char *b u f) ;
int setvbuf(FILEf p,* char *b u f, int m o d e, size_ts i z e) ;
返回:若成功则为0,若出错则为非0
这些函数一定要在流已被打开后调用(这是十分明显的,因为每个函数都要求一个有效的文件指针作为它们的第一个参数),而且也应在对该流执行任何一个其他操作之前调用。可以使用s e t b u f函数打开或关闭缓存机制。为了带缓存进行I / O,参数buf 必须指向一个长度为B U F S I Z的缓存(该常数定义在< s t d i o . h >;中)。通常在此之后该流就是全缓存的,但是如果该流与一个终端设备相关,那么某些系统也可将其设置为行缓存的。为了关闭缓存,将b u f设置为N U L L。
使用s e t v b u f,我们可以精确地说明所需的缓存类型。这是依靠m o d e参数实现的:
_IOFBF 全缓存
_IOLBF 行缓存
_IONBF 不带缓存
如果指定一个不带缓存的流,则忽略buf 和size 参数。如果指定全缓存或行缓存,则buf 和s i z e可以可选择地指定一个缓存及其长度。如果该流是带缓存的,而buf 是N U L L,则标准I / O库将自动地为该流分配适当长度的缓存。适当长度指的是由s t r u c t结构中的成员s t _ b l k s i z e所指定的值(见4 . 2节)。如果系统不能为该流决定此值(例如若此流涉及一个设备或一个管道),则分配长度为B U F S I Z的缓存。
表5 - 1列出了这两个函数的动作,以及它们的各个选择项。
这样一来,本文开头的两个例子就有很多修改的办法了。利用治本的方法解决改贴(http://bbs.chinaunix.net/forum/v ... der=asc&start=0)中的一个问题。
- #include <stdio.h>;
- void main()
- {
- int i,j;
- char c;
- printf("\n do you want to cal:y/n \n");
- while(c=getchar()=='y')
- {
- printf("input number:");
- scanf("%d%d",&i,&j);
- printf("i*j=%d",i*j);
- }
- }
复制代码
这个程序存在问题,这里使用setbuf()来“治疗”:
- #include <stdio.h>;
- int main(void)
- {
- int i,j;
- char c;
- printf("\n do you want to cal:y/n \n");
- while (c=getchar()=='y')
- {
- printf("input number:");
- scanf("%d%d",&i,&j);
- printf("i*j=%d",i*j);
- setbuf(stdin, NULL);
- printf("\n do you want to cal:y/n \n");
- }
- /*system("pause");*/
- return 0;
- }
复制代码
[img][/img] |
-
|