原文链接:http://blog.csdn.net/cxyol/archive/2006/03/18/628324.aspx

1.输入输出缓冲区的概念(C++用的多一些)

我想以一个例子说明,比如我想把一篇文章以字符序列的方式输出到计算机显示器屏幕上,那么我的程序内存作为数据源而显示器驱动程序作为数据目标,如果数据源直接对数据目标发送数据的话。数据目标获得第一个字符,便将它显示。然后从端口读取下一个字符,可是这时就不能保证数据源向端口发送的恰好是第二个字符(也许是第三个,而第二个已经在数据目标显示时发送过了)。这样的话就不能保证输出的数据能完整的被数据目标所接受并处理。

为了解决这个问题,我们需要在数据源与数据目标中间放置一个保存完整数据内容的区域,那就是 “缓冲区”。这样的话, 数据源可以不考虑数据目标正在处理哪部分数据,只要把数据输出到缓冲区就可以了,数据目标也可以不考虑数据源的发送频率,只是从缓冲区中依次取出下一个数据。从而保证了数据发送的完整性,同时也提高了程序的效率。

当然getch()getche()没有用到缓冲区。

2.几个函数的区别

首先不要忘了,要用getch()必须引入头文件conio.h ,以前学C语言的时候,我们总喜欢用在程序的末尾加上它,利用它来实现程序运行完了暂停不退出的效果。如果不加这句话,在TC2.0的环境中我们用Ctrl+F9编译并运行后,程序一运行完了就退回到TC环境中,我们根本来不及看到结果,这时要看结果,我们就要按Alt+F5回到DOS环境中去看结果,这很麻烦。而如果在程序的结尾加上一行 getch(); 语句,我们就可以省掉会DOS看结果这个步骤,因为程序运行完了并不退出,而是在程序最后把屏幕停住了,按任意键才退回到TC环境中去。

那我们来看看 getch() 到底起的什么作用, getch() 实际是一个输入命令,作用是从键盘接收一个字符,而且并不把这个字符显示出来,就是说,你按了一个键后它并不在屏幕上显示你按的什么,而继续运行后面的代码,所以我们在C++中可以用它来实现“按任意键继续”的效果,即程序中遇到getch();这行语句,它就会把程序暂停下来,等你按任意键,它接收了这个字符键后再继续执行后面的代码。

你也许会问,为什么我们在C++中就没有在程序的末尾加上 getch() ,解释是,软件总是不断更新的,不好的地方当然要进行改正, getch() 加在程序末尾,它又不赋值给任何变量,所以它在这个地方完全是垃圾代码,与程序无关。C++中考虑到这一点,于是在每次程序运行完了并不退出,而是自动把屏幕停下来,并显示 “press any key...” 叫你按任意键退出,这就好比C++在它的环境中运行程序,在程序的末尾自动加上了一行 getch(); 语句,并且在这行语句前还添加了一行输出语句 cout<<"press any key..."; 来提示你程序结束了,按任意键继续。

实际上我们编译好的程序在程序结束了本身是不会停下来的,我们可以在编译产生的Debug目录中找到这个编译好的应用程序(扩展名exe),在文件夹中双击运行它,你会发现屏幕闪了一下MS-DOS窗口就关闭了,因为程序运行完就自动退出了,回到了windows环境,当然,如果我们在DOS环境中运行这个程序,我们就可以直接在看到DOS屏幕上看到程序运行结果,因为程序运行完后并不清屏。但是,visual stdio.net2003有返回到了tc那样的情况,你必需要有个 getch() 才行。

getche()getch() 很相似,它也需要引入头文件 conio.h ,那它们之间的区别又在哪里呢?不同之处就在于 getch() 无返回显示,getche() 有返回显示。就这么一点看看下面的例子:

#include<stdio.h>
#include<conio.h>
void main()
{
    char ch;
    for(int i=0;i<5;i++)
    {
        ch=getch();
        printf("%c",ch);
    }
}

首先这是个连续5次的循环来实现5次停顿,等待我们输入,我们编译并运行这个程序,假设我们分别输入 abcde ,屏幕上显示的结果是 abcde ,这个 abcde 并不是在 ch=getch(); 中输出的,我们把 printf("%c",ch); 这行语句去掉,就会发现我们按5次任意键程序就结束了,但屏幕上什么都没有显示。

然后我们在把代码中的 getch() 换成 getche() 看看有什么不同,我们还是分别输入 abcde ,这时屏幕上显示的结果是 aabbccddee ,我们把 printf("%c",ch); 这行语句再去掉看看,显示的结果就是 abcde 了,说明程序在执行 ch=getche(); 这条语句的时候就把我们输入的键返回显示在屏幕上,有无回显就是它们的唯一区别。

有人会说,既然是C的函数库中的,那么就应该淘汰了,我们还研究它,还用它干嘛?但是我发现还是有用着它的地方,否则我也不会在这里说这么多来耽误大家的时间。我就举个例子吧,程序如下:

#include<stdio.h>
#include<conio.h>
void main()
{
    char ch='*';
    while(ch=='*')
    {
        printf("/n按 * 继续循环,按其他键退出!");
        ch=getch();
    }
    printf("/n退出程序!");
}

我们可以在这个循环体中添加我们想要的功能,程序中按*继续循环,其他任意键退出,而且利用 getch() 无回显的特性,我们不管按什么,都不会在屏幕上留下痕迹,使我们的界面达到美观效果,如果还有更好的办法实现这个功能。例子:

#include<stdio.h>
#include<conio.h>
void main()
{ 
    char c, ch;
    c=getch();     /*从键盘上读入一个字符不回显送给字符变量c*/
    putchar(c);    /*输出该字符*/
    ch=getche();   /*从键盘上带回显的读入一个字符送给字符变量ch*/
    putchar(ch);
    printf("/n/n");
}

值得注意的是前面两个函数都是从键盘读入数据!
还有 getchar 是很值得研究的:getchar()stdio.h 中的库函数,它的作用是从 stdin 流中读入一个字符,也就是说,如果 stdin 有数据的话不用输入它就可以直接读取了。而 getch()getche()conio.h 中的库函数,它的作用是从键盘接收字符。getchar 带有显示。

与前面两个函数的区别在于: getchar() 函数等待输入直到按回车才结束(前提是缓冲区没有数据),回车前的所有输入字符都会逐个显示在屏幕上。但只有第一个字符作为函数的返回值。

#include<stdio.h>
#include<conio.h>
void main()
{
    char c;
    c=getchar();   /*从键盘读入字符直到回车结束*/
    //getchar()在这里它只返回你输入字符串的第一个字符,并把返回值赋值给c
    putchar(c);    /*显示输入的第一个字符*/
    printf("/n/n");
}

例四:呵呵,这个程序你运行一下,相信你又会有疑问了。这个就是从缓冲区中读取了例子。第一次 getchar() 时,确实需要人工的输入,但是如果你输了多个字符,以后的 getchar() 再执行时就会直接从缓冲区中读取了。

#include<stdio.h>
#include<conio.h>
void main()
{
    char c;
    while ((c=getchar())!='/n')    /*每个getchar()依次读入一个字符*/
        printf("%c",c);        /*按照原样输出*/
    printf("/n/n");
}

程序运行时,首先停下来,等你输入一串字符串,输入完毕后,它把你输入的整个字符串都输出来了,咦,你不是说 getchar() 只返回第一个字符么,这里怎么?

因为我们输入的字符串并不是取了第一个字符就把剩下的字符串丢掉了,它还在我们的内存中,就好比,开闸放水,我们把水放到闸里去以后,开一次闸就放掉一点,开一次就放掉一点,直到放光了为止,这里开闸动作就相当于调用一次 getchar() 。我们输入的字符串也是这么一回事,首先我们输入的字符串是放在内存的缓冲区中的,我们调用一次 getchar() 就把缓冲区中里出口最近的一个字符输出,也就是最前面的一个字符输出,输出后,就把它释放掉了,但后面还有字符串,所以我们就用循环把最前面的一个字符一个个的在内存中释放掉,直到不满足循环条件退出为止。

例子中循环条件里的 '/n' 实际上就是你输入字符串后的回车符,所以意思就是说,直到遇到回车符才结束循环,而 getchar() 函数就是等待输入(或缓冲区中的数据)直到按回车才结束,所以实现了整个字符串的输出。当然,我们也可以把循环条件改一下,比如 while ((c=getchar())!='a') ,什么意思呢,意思就是遇到字符'a'就停止循环,当然意思是如果你输入 “12345a213123/n” 那么只会输出到 a ,结果是 12345a

再次注意:用 getchar() 它是从“流”中间去读取,所以第一个 getchar() 接受的是刚刚中断的流队列中即将出列的第一个字符(不限于回车符,上面举过例子了),如果流队列不为空,执行 getchar() 就继续放水,直到把回车符也放空为止,空了之后再在执行 getchar() 就停下等待你的输入了;我们用 getch() 为什么每次都是等待用户的输入呢?因为 getch() 是从键盘接收,即时的接收,并不是从 stdin 流中去读取数据。

补充:按键盘上的回车产生了2个字符:回车符('/r')和换行符('/n')。回车符'/r'(CR:carriage return:倒车)使光标回到这行的首部,换行符('/n')(new line)然后再换行。

所以当输入字符'w',并按下回车键以后。首先得到回车符。那个 getchar 函数结束了。 但是还存在一个换行符。所以如果用 getchar() 来做判断的时候。最好再写一次 getchar() 清除缓冲区的'/n'.

3.如何清空输入缓冲区的内容?

如果我想让 getchar() 每次都能够等待用户输入的话就要清空缓冲区,下面就介绍方法(不同平台)。

C标准规定 fflush() 函数是用来刷新输出(stdout)缓存的。对于输入(stdin),它是没有定义的。但是有些编译器也定义了 fflush( stdin ) 的实现,比如微软的VC。其它编译器是否也定义了 fflush( stdin ) 的实现应当查找它的手册。GCC编译器没有定义它的实现,所以不能使用 fflush( stdin ) 来刷新输入缓存。

对于没有定义 fflush( stdin ) 的编译器,可以使用 fgets() 函数来代替它(比用 getchar()scanf() 等函数通用性好)。可以这样忽略输入流中留下的回车等其它输入,从而使下一次的输入总保持一个“干净”的状态。(这个是任何平台下都可以的)

// ...
char sbuf[1024];
// ...
fgets( sbuf, 1024, stdin );
// ...
在windows 的vc下面就可以这样了:
for(int i=0;i<10;++i)
{
   char ch=getchar();
   fflush(stdin); //每次都会有等待状态了
}

4.总结

主要看 getch()getche() 的是否显示,getchar() 是读取流,而且和前面两个函数不是一个库。掌握清空缓冲区的方法。

Original Link: http://ibillxia.github.io/blog/2009/06/05/io-buffer-in-c-language/
Attribution - NON-Commercial - ShareAlike - Copyright © Bill Xia