C语言编程 - 清空键盘缓冲区

简介: 本文来源:CSDN博客,连接:http://blog.csdn.net/dragonszy/article/details/8441532 清空键盘缓冲区很多种方法,如用fflush(stdin); rewind(stdin);等,但是在linux这些都不起作用,还得我今天试了半天都没成功,上网搜了一下发现setbuf(stdin, NULL);就能直接清空键盘缓冲区了。

本文来源:CSDN博客,连接:http://blog.csdn.net/dragonszy/article/details/8441532

清空键盘缓冲区很多种方法,如用fflush(stdin); rewind(stdin);等,但是在linux这些都不起作用,还得我今天试了半天都没成功,上网搜了一下发现setbuf(stdin, NULL);就能直接清空键盘缓冲区了。

以下几个实例:

Sample one

#include <stdio.h>

int main()
{
    char ch1;
    char ch2;

    ch1 = getchar();
    ch2 = getchar();
    printf("%d  %d", ch1, ch2);
    return 0;
}

程序的本意很简单,就是从键盘读入两个字符,然后打印出这两个字符的ASCII码值。可是执行程序后会发现出了问题:当从键盘输入一个字符后,就打印出了结果,根本就没有输入第二个字符程序就结束了。例如用户输入字符’a', 打印结果是97,10。这是为什么呢?【PS:输入时先输入字符‘a’然后回车,然后想输入第二个字符‘b’的时候就已经没机会输入了,直接输出,但输出是“97  10”。假如输入是这样的:先输入字符‘a’然后空格,然后输入第二个字符‘b’,再回车。这样输出是“97  32”。注意:回车符号代表的其实是换行符号,它的ASCII码是10,空格的ASCII码是32。】

【分析】:

     scanf()和getchar()函数是从输入流缓冲区中读取值的,而并非从键盘(也就是终端)缓冲区读取。而读取时遇到回车(\n)而结束的,这个\n会一起读入输入流缓冲区的,所以第一次接受输入时取走字符后会留下字符\n,这样第二次的读入函数直接从缓冲区中把\n取走了,显然读取成功了,所以不会再从终端读取!其实这里的10恰好是回车符!这就是为什么这个程序只执行了一次输入操作就结束的原因!

【解决办法】:

     清空缓冲区的残留数据。

     使用 fflush(stdin); 或 rewind(stdin); 均可起到清空键盘缓冲区的作用,这两个函数均包含在stdio.h这个头文件中

修正后的写法:
Sample two

 1 /*
 2  * 本程序只适用于 Windows 系统,测试平台:
 3  * Windows XP,Microsoft Visual C++ 6.0 SP6
 4 */
 5 
 6 #include <stdio.h>
 7 
 8 int main()
 9 {
10     char ch1;
11     char ch2;
12 
13     scanf("%c", &ch1);
14     printf("ch1 = %d", ch1);
15 
16     fflush(stdin);   /*清空缓冲区,也可以使用rewind(stdin);*/
17 
18     scanf("%c", &ch2);
19     printf("ch2 = %d", ch2);
20     return 0;
21 }

上面的实例只适用于Windows系统,在Linux环境下上面两种写法都是不起作用的,所以还要换个函数。

Sample three

 1 /*
 2 * 本程序适用于 Windows 和 Linux 系统,
 3 * 测试环境:
 4 * Windows XP,Microsoft Visual C++ 6.0 SP6
 5 * Ubuntu Linux 8.04, NetBeans IDE 6.7
 6 */
 7 
 8 #include <stdio.h>
 9 
10 int main()
11 {
12     char ch1;
13     char ch2;
14 
15     scanf("%c", &ch1);
16     printf("ch1 = %d", ch1);
17 
18     setbuf(stdin, NULL); /*清空缓冲区*/
19 
20     scanf("%c", &ch2);
21     printf("ch2 = %d", ch2);
22     return 0;
23 }

 

---

 

source:http://zhidao.baidu.com/question/191469857.html?fr=qrl&cid=866&index=1&fr2=query

 

fflush()函数是标准的作法。

setbuf(stdin,NULL)是GCC下可用的一种方法。

scanf("%*[^\n]%*c")是用扫描集将缓冲区中的字符全部读取来实现清除缓冲区的动作。

我最近在ubuntu下写一个程序,涉及到了这方面的问题。用fflush()和setbuf(stdin,NULL)都没有解决掉缓存的问题。 (奇怪的错误,DT地不想调试。)后来,搜到有 scanf("%*[^\n]%*c") 这种方法。非常管用,而且还跨平台。

  scanf("%*[^\n]%*c")解释:

%*〔^\n〕将逐个读取缓冲区中的'\n'字符之前的其它字符,%后面的*表示将读取的这些字符丢弃,前遇到'\n'字符时便停止读取操作,此时,缓冲区中尚有一个'\n'字符遗留,所以后面的%*c将读取并丢弃这个遗留的换行符,这里的星号和前面的星号作用相同。由于所有从键盘的输入都是以回车结束的,而回车会产生一个'\n'字符,所以将'\n'连同它之前的字符全部读取并丢弃之后,也就相当于清除了输入缓冲区。

【PS:下面讲的实在是啰嗦。其实在OJ上提交代码,往往是空格分隔的字符才有这个输入bug的问题。我的处理方法很简单很暴力:直接用一个char类型变量来接收那个多余的、烦人的空格或者回车,或者在需要回车的地方加一个getchar就OK了。其实看完上面的就能够解决问题了。有兴趣的可以继续往下看。】

 

 

C语言清空输入缓冲区的N种方法对比

 

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结构体源码

 

typedef struct _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()实现

__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成功返回时,将输入缓冲区中的数据连换行符’\n’一起拷贝到第一个参数所指向的空间中。若输入数据超过缓冲区长度,fgets会截取数据到前n-1(n为fgets第二个参数,为第一个参数指向空间的长度),然后在末尾加入’\n’。因此fgets是安全的。通常用fgets(buf, BUF_LEN, stdin);代替gets(buf);。
  • 格式化输入系列中,fscanf从文件流进行格式化输入很不好用。常用的还是scanf,格式化输入系列函数舍去输入数据(根据函数不同可能是标准输入也可能是字符串输入,如:sscanf)前的空白字符(空格、制表符、换行符)直至遇到非空白字符,然后根据格式参数尝试对非空白字符及后续字符进行解析。该系列函数返回成功解析赋值的变量数,若遇文件尾或错误,返回EOF。

=================分 割 线=================

 

 

提到缓冲区,就不得不提setbufsetvbuf两个缓冲区设置函数,其声明如下:

void setbuf(FILE * restrict stream, char * 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陷阱和缺陷》上有提到:

int 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("%*[^\n]");式(《C语言程序设计 现代方法 第二版》中提到)

这里用到了scanf格式化符中的“*”,即赋值屏蔽;“%[^集合]”,匹配不在集合中的任意字符序列。这也带来个问题,缓冲区中的换行符’\n’会留下来,需要额外操作来单独丢弃换行符。

  • 经典式
int c;
while((c = getchar()) != '\n' && c != EOF);

由代码知,不停地使用getchar()获取缓冲区中字符,直到获取的字符c是换行符’\n’或者是文件结尾符EOF为止。这个方法可以完美清除输入缓冲区,并且具备可移植性。

 

 

 

 

 

 

 

/*
 * 本程序只适用于 Windows 系统,测试平台:
 * Windows XP,Microsoft Visual C++ 6.0 SP6
*/

#include <stdio.h>

int main()
{
    char ch1;
    char ch2;

    scanf("%c", &ch1);
    printf("ch1 = %d", ch1);

    fflush(stdin);   /*清空缓冲区,也可以使用rewind(stdin);*/

    scanf("%c", &ch2);
    printf("ch2 = %d", ch2);
    return 0;
}

相关文章
|
26天前
|
存储 编译器 C语言
如何在 C 语言中判断文件缓冲区是否需要刷新?
在C语言中,可以通过检查文件流的内部状态或使用`fflush`函数尝试刷新缓冲区来判断文件缓冲区是否需要刷新。通常,当缓冲区满、遇到换行符或显式调用`fflush`时,缓冲区会自动刷新。
|
26天前
|
存储 编译器 C语言
C语言:文件缓冲区刷新方式有几种
C语言中文件缓冲区的刷新方式主要包括三种:自动刷新(如遇到换行符或缓冲区满)、显式调用 fflush() 函数强制刷新、以及关闭文件时自动刷新。这些方法确保数据及时写入文件。
|
1月前
|
NoSQL C语言 索引
十二个C语言新手编程时常犯的错误及解决方式
C语言初学者常遇错误包括语法错误、未初始化变量、数组越界、指针错误、函数声明与定义不匹配、忘记包含头文件、格式化字符串错误、忘记返回值、内存泄漏、逻辑错误、字符串未正确终止及递归无退出条件。解决方法涉及仔细检查代码、初始化变量、确保索引有效、正确使用指针与格式化字符串、包含必要头文件、使用调试工具跟踪逻辑、避免内存泄漏及确保递归有基准情况。利用调试器、编写注释及查阅资料也有助于提高编程效率。避免这些错误可使代码更稳定、高效。
220 12
|
2月前
|
存储 算法 Linux
C语言 多进程编程(一)进程创建
本文详细介绍了Linux系统中的进程管理。首先,文章解释了进程的概念及其特点,强调了进程作为操作系统中独立可调度实体的重要性。文章还深入讲解了Linux下的进程管理,包括如何获取进程ID、进程地址空间、虚拟地址与物理地址的区别,以及进程状态管理和优先级设置等内容。此外,还介绍了常用进程管理命令如`ps`、`top`、`pstree`和`kill`的使用方法。最后,文章讨论了进程的创建、退出和等待机制,并展示了如何通过`fork()`、`exec`家族函数以及`wait()`和`waitpid()`函数来管理和控制进程。此外,还介绍了守护进程的创建方法。
C语言 多进程编程(一)进程创建
|
2月前
|
Linux C语言
C语言 多进程编程(三)信号处理方式和自定义处理函数
本文详细介绍了Linux系统中进程间通信的关键机制——信号。首先解释了信号作为一种异步通知机制的特点及其主要来源,接着列举了常见的信号类型及其定义。文章进一步探讨了信号的处理流程和Linux中处理信号的方式,包括忽略信号、捕捉信号以及执行默认操作。此外,通过具体示例演示了如何创建子进程并通过信号进行控制。最后,讲解了如何通过`signal`函数自定义信号处理函数,并提供了完整的示例代码,展示了父子进程之间通过信号进行通信的过程。
|
2月前
|
Linux C语言
C语言 多进程编程(四)定时器信号和子进程退出信号
本文详细介绍了Linux系统中的定时器信号及其相关函数。首先,文章解释了`SIGALRM`信号的作用及应用场景,包括计时器、超时重试和定时任务等。接着介绍了`alarm()`函数,展示了如何设置定时器以及其局限性。随后探讨了`setitimer()`函数,比较了它与`alarm()`的不同之处,包括定时器类型、精度和支持的定时器数量等方面。最后,文章讲解了子进程退出时如何利用`SIGCHLD`信号,提供了示例代码展示如何处理子进程退出信号,避免僵尸进程问题。
|
2月前
|
消息中间件 Unix Linux
C语言 多进程编程(五)消息队列
本文介绍了Linux系统中多进程通信之消息队列的使用方法。首先通过`ftok()`函数生成消息队列的唯一ID,然后使用`msgget()`创建消息队列,并通过`msgctl()`进行操作,如删除队列。接着,通过`msgsnd()`函数发送消息到消息队列,使用`msgrcv()`函数从队列中接收消息。文章提供了详细的函数原型、参数说明及示例代码,帮助读者理解和应用消息队列进行进程间通信。
|
2月前
|
缓存 Linux C语言
C语言 多进程编程(六)共享内存
本文介绍了Linux系统下的多进程通信机制——共享内存的使用方法。首先详细讲解了如何通过`shmget()`函数创建共享内存,并提供了示例代码。接着介绍了如何利用`shmctl()`函数删除共享内存。随后,文章解释了共享内存映射的概念及其实现方法,包括使用`shmat()`函数进行映射以及使用`shmdt()`函数解除映射,并给出了相应的示例代码。最后,展示了如何在共享内存中读写数据的具体操作流程。
|
2月前
|
消息中间件 Unix Linux
C语言 多进程编程(二)管道
本文详细介绍了Linux下的进程间通信(IPC),重点讨论了管道通信机制。首先,文章概述了进程间通信的基本概念及重要性,并列举了几种常见的IPC方式。接着深入探讨了管道通信,包括无名管道(匿名管道)和有名管道(命名管道)。无名管道主要用于父子进程间的单向通信,有名管道则可用于任意进程间的通信。文中提供了丰富的示例代码,展示了如何使用`pipe()`和`mkfifo()`函数创建管道,并通过实例演示了如何利用管道进行进程间的消息传递。此外,还分析了管道的特点、优缺点以及如何通过`errno`判断管道是否存在,帮助读者更好地理解和应用管道通信技术。
|
2月前
|
存储 Ubuntu Linux
C语言 多线程编程(1) 初识线程和条件变量
本文档详细介绍了多线程的概念、相关命令及线程的操作方法。首先解释了线程的定义及其与进程的关系,接着对比了线程与进程的区别。随后介绍了如何在 Linux 系统中使用 `pidstat`、`top` 和 `ps` 命令查看线程信息。文档还探讨了多进程和多线程模式各自的优缺点及适用场景,并详细讲解了如何使用 POSIX 线程库创建、退出、等待和取消线程。此外,还介绍了线程分离的概念和方法,并提供了多个示例代码帮助理解。最后,深入探讨了线程间的通讯机制、互斥锁和条件变量的使用,通过具体示例展示了如何实现生产者与消费者的同步模型。