转自百度文库
fflush详解
1、为什么 fflush(stdin) 是错的首先请看以下程序:
#include <stdio.h>
int main( void )
{
int i;
for (;;)
{
fputs("Please input an integer: ", stdout);
scanf("%d", &i);
printf("%d\n", i);
}
return 0;
}
这个程序首先会提示用户输入一个整数,然后等待用户输入,如果用户输入的是整数,程序会输出刚才输入的整数,并且再次提示用户输入一个整数,然后等待用户输入。但是一旦用户输入的不是整数(如小数或者字母),假设 scanf 函数最后一次得到的整数是 2 ,那么程序会不停地输出“Please input an integer: 2”。这是因为 scanf("%d", &i); 只能接受整数,如果用户输入了字母,则这个字母会遗留在“输入缓冲区”中。因为缓冲中有数据,故而 scanf 函数不会等待用户输入,直接就去缓冲中读取,可是缓冲中的却是字母,这个字母再次被遗留在缓冲中,如此反复,从而导致不停地输出“Please input an integer: 2”。
也许有人会说:“居然这样,那么在 scanf 函数后面加上‘fflush(stdin);’,把输入缓冲清空掉不就行了?”然而这是错的!C和C++的标准里从来没有定义过 fflush(stdin)。也许有人会说:“可是我用 fflush(stdin) 解决了这个问题,你怎么能说是错的呢?”的确,某些编译器(如VC6)支持用 fflush(stdin) 来清空输入缓冲,但是并非所有编译器都要支持这个功能(linux 下的 gcc 就不支持),因为标准中根本没有定义 fflush(stdin)。MSDN 文档里也清楚地写着fflush on input stream is an extension to the C standard(fflush 操作输入流是对 C 标准的扩充)。当然,如果你毫不在乎程序的移植性,用 fflush(stdin) 也没什么大问题。以下是 C99 对 fflush 函数的定义:
int fflush(FILE *stream);
如果 stream 指向输出流或者更新流(update stream),并且这个更新流
最近执行的操作不是输入,那么 fflush 函数将把这个流中任何待写数据传送至
宿主环境(host environment)写入文件。否则,它的行为是未定义的。
原文如下:
int fflush(FILE *stream);
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.
其中,宿主环境可以理解为操作系统或内核等。
由此可知,如果 stream 指向输入流(如 stdin),那么 fflush 函数的行为是不确定的。故而使用 fflush(stdin) 是不正确的,至少是移植性不好的。
2、清空输入缓冲区的方法
虽然不可以用 fflush(stdin),但是我们可以自己写代码来清空输入缓冲区。只需要在 scanf 函数后面加上几句简单的代码就可以了。
/* C 版本 */
#include <stdio.h>
int main( void )
{
int i, c;
for ( ; ; )
{
fputs("Please input an integer: ", stdout);
scanf("%d", &i);
if ( feof(stdin) || ferror(stdin) )
{ /* 如果用户输入文件结束标志(或文件已被读完), */
/* 或者发生读写错误,则退出循环 */
/* do something */
break;
}
/* 没有发生错误,清空输入流。 */
/* 通过 while 循环把输入流中的余留数据“吃”掉 */
while ( (c = getchar()) != '\n' && c != EOF ) ;
/* 使用 scanf("%*[^\n]"); 也可以清空输入流, */
/* 不过会残留 \n 字符。 */
printf("%d\n", i);
}
return 0;
}
/* C++ 版本 */
#include <iostream>
#include <limits> // 为了使用numeric_limits
using std::cout;
using std::endl;
using std::cin;
using std::numeric_limits;
using std::streamsize;
int main()
{
int value;
for ( ; ; )
{
cout << "Enter an integer: ";
cin >> value;
if ( cin.eof() || cin.bad() )
{ // 如果用户输入文件结束标志(或文件已被读完),
// 或者发生读写错误,则退出循环
// do something
break;
}
// 读到非法字符后,输入流将处于出错状态,
// 为了继续获取输入,首先要调用 clear 函数
// 来清除输入流的错误标记,然后才能调用
// ignore 函数来清除输入流中的数据。
cin.clear();
// numeric_limits<streamsize>::max() 返回输入缓冲的大小。
// ignore 函数在此将把输入流中的数据清空。
// 这两个函数的具体用法请读者自行查询。
cin.ignore( numeric_limits<streamsize>::max(), '\n' );
cout << value << '\n';
}
return 0;
}
标准I / O提供缓存的目的是尽可能减少使用read和write调用的数量。它也对每个I / O流自动地进行缓存管理,避免了应用程序需要考虑这一点所带来的麻烦。不幸的是,标准I / O库令人最感迷惑的也是它的缓存。不同类型缓存往往使人在进行I/O操作时不知所措。标准I / O提供了三种类型的缓存:全缓存、行缓存、无缓存。
说的最清楚的要数Stevens的《UNIX环境高级编程》了,以下摘自Stevens的《UNIX环境高级编程》第五章:
//******************
标准I / O提供了三种类型的缓存:
(1) 全缓存。在这种情况下,当填满标准I / O缓存后才进行实际I / O操作。对于驻在磁盘上的文件通常是由标准I / O库实施全缓存的。在一个流上执行第一次I / O操作时,相关标准I / O函数通常调用m a l l o c(见7 . 8节)获得需使用的缓存。
术语刷新( f l u s h)说明标准I / O缓存的写操作。缓存可由标准I / O例程自动地刷新(例如当填满一个缓存时),或者可以调用函数ff l u s h刷新一个流。值得引起注意的是在U N I X环境中,刷新有两种意思。在标准I / O库方面,刷新意味着将缓存中的内容写到磁盘上(该缓存可以只是局部填写的)。在终端驱动程序方面(例如在第11章中所述的t c f l u s h函数),刷新表示丢弃已存在缓存中的数据。
(2) 行缓存。在这种情况下,当在输入和输出中遇到新行符时,标准I / O库执行I / O操作。这允许我们一次输出一个字符(用标准I/O fputc函数),但只有在写了一行之后才进行实际I / O操作。当流涉及一个终端时(例如标准输入和标准输出),典型地使用行缓存。对于行缓存有两个限制。第一个是:因为标准I / O库用来收集每一行的缓存的长度是固定的,所以只要填满了缓存,那么即使还没有写一个新行符,也进行I / O操作。第二个是:任何时候只要通过标准输入输出库要求从( a )一个不带缓存的流,或者( b )一个行缓存的流(它预先要求从内核得到数据)得到输入数据,那么就会造成刷新所有行缓存输出流。在( b )中带了一
个在括号中的说明的理由是,所需的数据可能已在该缓存中,它并不要求内核在需要该数据时才进行该操作。很明显,从不带缓存的一个流中进行输入( ( a )项)要求当时从内核得到数据。
(3) 不带缓存。标准I / O库不对字符进行缓存。如果用标准I / O函数写若干字符到不带缓存
的流中,则相当于用w r i t e系统调用函数将这些字符写至相关联的打开文件上。标准出错流s t d e r r通常是不带缓存的,这就使得出错信息可以尽快显示出来,而不管它们是否含有一个新行字符。
ANSI C要求下列缓存特征:
(1) 当且仅当标准输入和标准输出并不涉及交互作用设备时,它们才是全缓存的。
(2) 标准出错决不会是全缓存的。
但是,这并没有告诉我们如果标准输入和输出涉及交互作用设备时,它们是不带缓存的还
是行缓存的,以及标准输出是不带缓存的,还是行缓存的。S V R 4和4 . 3 + B S D的系统默认使用下列类型的缓存:
? 标准出错是不带缓存的。
? 如若是涉及终端设备的其他流,则它们是行缓存的;否则是全缓存的。
//****************************
*
我们经常要用到标准输入和输出,而ANSI C对stdin、stdout和stderr的缓存特征没有强行的规定,以至于不同的系统可能有不同的stdin、stdout和stderr的缓存特征。目前主要的缓存特征是:stdin和stdout是行缓存;而stderr是无缓存的。