计算机CPU的处理速度很快,而键盘的输入速度总是比不过CPU的处理速度,如果CPU一直等待键盘输入完,这样很浪费资源。为了解决速度的不匹配,引入缓冲区。
缓冲区就是内存里的一块区域,把数据先存内存里,然后一次性写入,类似于数据库的批量操作,这样大大提高高了数据的读写速率。
现在的外设控制器也有自己的缓冲池,如磁盘和网卡,通过缓冲IO的方式读取数据。
1 缓冲区的类型
缓冲区分为三种类型:全缓冲、行缓冲和不带缓冲。
全缓冲
只有当划定的缓冲区被填满或者数据读取至末尾时,才开始执行 I\O 操作(执行系统提供的 read\write 操作)。磁盘文件的读写一般采用这种方式。行缓冲
当输入输出过程遇到换行符''\n"或者当分配缓冲区已满时,才开始执行 I\O 操作。一般涉及终端的读写操作如 stdin 与 stdout 使用这种缓冲方式。不带缓冲
当有数据产生时,马上由相应的设备进行处理。一般来说 stderr(standard error) 使用这种缓冲方式,使得有错误信息时马上能够得到响应。需要注意的是,标准库不缓存并不意味着操作系统或者设备驱动不缓存。
2 缓冲区的刷新
缓冲区会在以下三种情况下被刷新:
缓冲区满
执行flush刷新缓冲区的语句
程序正常结束。
3 行缓冲
标准输入缓冲区 stdin 使用行缓冲的方式存储输入。
用户的输入数据首先被暂存在临时缓冲区中,当用户键入回车键或临时缓冲区满后,stdin 才进行 I/O 操作,将数据由临时缓冲区拷贝至 stdin 中。
C语言提供的输入输出函数如 scanf 、getchar 等则从上述缓冲区 stdin 中读取数据输入。
4 getchar()
getchar()是stdio.h中的库函数,它的作用是从stdin流中读出第一个字符(包括空格、回车和Tab) 并清除,并不是直接从键盘这个硬件中读取输入的字符。
如果stdin有数据的话不用输入它就可以直接读取了,第一次getchar()时,确实需要人工的输入,但是如果输入多个字符,以后的getchar()再执行时就会直接从缓冲区中读取。
下面的语句可以清除回车:
while(getchar()!='\n');
分析以下代码:
#include <stdio.h>
int main()
{
char c;
while((c = getchar()) != '\n')
putchar(c);
return 0;
}
程序运行过程:
getchar()会从输入缓冲区去读取内容。当所有的内容都输入完成并且按下了Enter键后,我们的输入才被送进去了输入缓冲区,这个时候,while循环才开始工作,每一次getchar()从输入缓冲区读取一个字符,然后如果不是换行符就输出。
在调用第一个getchar时输入了多个字符,那么,加入一个getchar并不能把所有未读取的字符过滤。如果希望重新从“键盘”读取的话,最好是加一个fflush(stdin):清除输入缓冲区
#include <stdio.h>
int main()
{
char str[1000];
char option='y';
while(option!='n' && option!='N')
{
scanf( "%[^\n]", str );
printf( "%s\n", str );
printf("还要继续吗?");
scanf("%c",&option);
}
return 0;
}
运行结果:
改进代码:
#include <stdio.h>
int main()
{
char str[1000];
char option='y';
while(option!='n' && option!='N')
{
scanf( "%[^\n]", str );
printf( "%s\n", str );
fflush(stdin);
printf("还要继续吗?");
scanf("%c",&option);
}
}
运行结果:
#include <stdio.h>
int main()
{
char str[1000];
char option='y';
while(option!='n' && option!='N')
{
fflush(stdin);
fgets(str, 1000, stdin);//尝试使用fgets
printf( "%s\n", str );
printf("还要继续吗?");
scanf("%c",&option);
}
}
运行结果:
5 scanf()
函数声明:int scanf( format string , arg1 , arg2 , ...);
从函数声明可以看到,scanf 的参数由指示读取动作的格式化字符串( format string )和相应的地址参数 arg1...argn 组成。scanf 函数将输入从标准输入缓冲区 stdin 中读入,并将它们以格式化字符串中指定的格式存储到额外的参数 arg1...arg2 等指定的内存空间中。其中额外的参数(additional argument)指向的内存空间的数据类型应该与格式化字符串中指定的数据类型相一致。
格式化字符串(format string)
格式化字符串规定了 scanf 等函数如何从输入缓冲 stdin 中读取数据,其组成字符的含义如下所示:
(1)空白字符(whitespace)。scanf 会读取并忽略在 stdin 中下一个非空白字符之前的所有空白字符(空格、换行和 tab),然后读取格式化字符串中规定格式的数据。若格式化字符串中包含空白字符,则该空白字符会与输入缓冲区中任意数量的连续空白字符相匹配,并将其从缓冲区中清除(包括0个)。例如格式化字符串"%d %d",会要求 scanf 首先从缓冲区中读取一个整型(若之前存在空白字符则跳过),再跳过输入缓冲区中连续的空白字符(与格式化字符串中的空白字符匹配),最后再读取一个整形;
(2)非空白字符(non whitespace)。对于格式化字符串中既非空白字符又不是格式说明符(format specifier,由%标识)的一部分的字符,scanf 会尝试从 stdin 中读取输入,并将输入与该字符比较,若匹配,则继续进行后续读取,若不匹配,则函数返回错误信息;
(3)格式说明符。以 % 开头的用于指定输入数据格式的字符。如 %d 指定需要读取一个整形,%s 需要读取一个字符串。scanf 等函数首先根据格式说明符尝试去解析 stdin 中的数据,如对于 %d ,scanf 会尝试对 stdin 中已有数据以整型的格式进行解析。若解析成功,则将上述解析结果存放到指定的内存中,若解析失败,如 stdin 中仅存在一个字符 'a',scanf 会退出并返回,但是上述不匹配的数据并不会从缓冲区中清除,后续的 scanf 调用仍从上述输入开始读取;
https://www.cnblogs.com/yhjoker/p/7530837.html
scanf异常处理
#include <stdio.h>
int main()
{
int a, ret;
ret = scanf("%d",&a);
printf("%d%d\n",ret, a);
while (ret != 1)
{
while (getchar() != '\n');
ret = scanf("%d",&a);
printf("%d%d\n",ret, a);
}
}