C语言中有几个基本输入函数:
- //获取字符系列
登录后复制
- int fgetc(FILE *stream);
登录后复制
- int getc(FILE *stream);
登录后复制
- int getchar(void);
登录后复制
- //获取行系列
登录后复制
- char *fgets(char * restrict s, int n, FILE * restrict stream);
登录后复制
- char *gets(char *s);//可能导致溢出,用fgets代替之。
登录后复制
- //格式化输入系列
登录后复制
- int fscanf(FILE * restrict stream, const char * restrict format, …);
登录后复制
- int scanf(const char * restrict format, …);
登录后复制
- int sscanf(const char * restrict str, const char * restrict format, …);
登录后复制
这里仅讨论输入函数在标准输入(stdin)情况下的使用。纵观上述各输入函数,
获取字符系列的的前三个函数fgetc、getc、getchar。以getchar为例,将在stdin缓冲区为空时,等待输入,直到回车换行时函数返回。若stdin缓冲区不为空,getchar直接返回。getchar返回时从缓冲区中取出一个字符,并将其转换为int,返回此int值。
MINGW 4.4.3中FILE结构体源码:
- _iobuf
登录后复制
- {
登录后复制登录后复制登录后复制
- char*_ptr;//指向当前缓冲区读取位置
登录后复制
- int_cnt;//缓冲区中剩余数据长度
登录后复制
- char*_base;
登录后复制
- int_flag;
登录后复制
- int_file;
登录后复制
- int_charbuf;
登录后复制
- int_bufsiz;
登录后复制
- char*_tmpfname;
登录后复制
- } FILE;
登录后复制
各编译器实现可能不一样,这里获取字符系列函数只用到_ptr和_cnt。
MINGW 4.4.3中getchar()实现:
立即学习“C语言免费学习笔记(深入)”;
- __CRT_INLINE int __cdecl __MINGW_NOTHROW getchar (void)
登录后复制
- {
登录后复制登录后复制登录后复制
- return (--stdin->_cnt >= 0)
登录后复制
- ? (int) (unsigned char) *stdin->_ptr++
登录后复制
- : _filbuf (stdin);
登录后复制
- }
登录后复制登录后复制
其中stdin为FILE指针类型,在MINGW 4.4.3中,getc()和getchar()实现为内联函数,fgetc()实现为函数。顺便说一句,C99标准中已经加入对内联函数的支持了。
获取行系列的fgets和gets,其中由于gets无法确定缓冲区大小,常导致溢出情况,这里不推荐也不讨论gets函数。对于fgets函数,每次敲入回车,fgets即返回。fgets成功返回时,将输入缓冲区中的数据连换行符’’一起拷贝到第一个参数所指向的空间中。若输入数据超过缓冲区长度,fgets会截取数据到前n-1(n为fgets第二个参数,为第一个参数指向空间的长度),然后在末尾加入’’。因此fgets是安全的。通常用fgets(buf, BUF_LEN, stdin);代替gets(buf);。
格式化输入系列中,fscanf从文件流进行格式化输入很不好用。常用的还是scanf,格式化输入系列函数舍去输入数据(根据函数不同可能是标准输入也可能是字符串输入,如:sscanf)前的空白字符(空格、制表符、换行符)直至遇到非空白字符,然后根据格式参数尝试对非空白字符及后续字符进行解析。该系列函数返回成功解析赋值的变量数,若遇文件尾或错误,返回EOF。
=================分 割 线=================
提到缓冲区,就不得不提setbuf和setvbuf两个缓冲区设置函数,其声明如下:
- setbuf(FILE * restrict stream, * restrict buf);
登录后复制
- int setvbuf(FILE * restrict stream, char * restrict buf, int mode, size_t size);
登录后复制
setvbuf的mode参数有:
_IOFBF(满缓冲):缓冲区空时读入数据;缓冲区满时向流写入数据。
_IOLBF(行缓冲):每次从流读入一行数据或向流写入数据。如:stdio,stdout
_IONBF(无缓冲):直接从流读入数据,或者直接向流写入数据,而没有缓冲区。如:stderr
setbuf(stream, buf);在:
buf == NULL:等价于(void)setvbuf(stream, NULL, _IONBF, 0);
buf指向长度为BUFSIZ的缓冲区:等价于(void)setvbuf(stream, buf, _IOFBF, BUFSIZ);
注:BUFSIZ宏在stdio.h中定义。
这里还要提一下传说中的setbuf的经典错误,在《C陷阱和缺陷》上有提到:
- main()
登录后复制
- {
登录后复制登录后复制登录后复制
- int c;
登录后复制
- char buf[BUFSIZ];
登录后复制
登录后复制
- setbuf(stdout,buf);
登录后复制
- while((c = getchar()) != EOF)
登录后复制
- putchar(c);
登录后复制
登录后复制
- return 0;
登录后复制
- }
登录后复制登录后复制
问题是这样的:程序交回控制给操作系统之前C运行库必须进行清理工作,其中一部分是刷新输出缓冲,但是此时main函数已经运行完毕,buf缓冲区作用域在main函数中,此时buf字符数组已经释放,导致输出诡异乱码。
解决方案:可以将buf设置为static,或者全局变量,或者调用malloc来动态申请内存。
=================分 割 线=================
下面来看看几种流行的缓冲区清空方法:
fflush(stdin);式
由C99标准文档中:
- 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.
登录后复制
可以看出fflush对输入流为参数的行为并未定义。但由MSDN上的fflush定义:
- If the file associated with stream is open for output, fflush writes to that file the
登录后复制
- contents of the buffer associated with the stream. If the stream is open for input,
登录后复制
- fflush clears the contents of the buffer.
登录后复制
可以看出fflush(stdin)在VC上还是有效地!鉴于各编译器对fflush的未定义行为实现不一样,不推荐使用fflush(stdin)刷新输入缓冲区。
setbuf(stdin, NULL);式
由前面对setbuf函数的介绍,可以得知,setbuf(stdin, NULL);是使stdin输入流由默认缓冲区转为无缓冲区。都没有缓冲区了,当然缓冲区数据残留问题会解决。但这并不是我们想要的。
scanf(“%*[^]”);式(《C语言程序设计 现代方法 第二版》中提到)
这里用到了scanf格式化符中的“*”,即赋值屏蔽;“%[^集合]”,匹配不在集合中的任意字符序列。这也带来个问题,缓冲区中的换行符’’会留下来,需要额外操作来单独丢弃换行符。
经典式
- c;
登录后复制
- while((c = getchar()) != '' && c != EOF);
登录后复制
由代码知,不停地使用getchar()获取缓冲区中字符,直到获取的字符c是换行符’’或者是文件结尾符EOF为止。这个方法可以完美清除输入缓冲区,并且具备可移植性。
相关文章:
禁止页面缓存的方法 多语言下禁止页面缓存
如何批量清理系统临时文件(语言:C#、 C/C++、 php 、python 、java )
相关视频:
C 语言教程
以上就是使用C语言怎样清空输入缓冲区?这里有多种方法值得借鉴的详细内容,更多请关注【创想鸟】其它相关文章!