scanf函数读取缓冲区数据的问题
标准I\O的缓冲类型
标准I\O根据不同的应用需求,提供了全缓冲、行缓冲、无缓冲三种缓冲方式。
全缓冲:只有当划定的缓冲区被填满或者数据读取至末尾时,才开始执行 I\O 操作(执行系统提供的 read\write 操作)。磁盘文件的读写一般采用这种方式。
行缓冲:当输入输出过程遇到换行符''\n"或者当分配缓冲区已满时,才开始执行 I\O 操作。一般涉及终端的读写操作如 stdin 与 stdout 使用这种缓冲方式。
无缓冲:当有数据产生时,马上由相应的设备进行处理。一般来说 stderr(standard error) 使用这种缓冲方式,使得有错误信息时马上能够得到响应。需要注意的是,标准库不缓存并不意味着操作系统或者设备驱动不缓存。
注意,以上关于 stdin/stdout 的缓冲方式并不是直接规定死的。一些语言的语言规范会对缓冲实现给出一定的限制,但并不具体,只是许多标准I/O是以上述方式实现的而已。可以参考关于流和缓冲区。
行缓冲
标准输入缓冲区 stdin 使用行缓冲的方式存储输入。用户的输入数据首先被暂存在临时缓冲区中,当用户键入回车键或临时缓冲区满后,stdin 才进行 I/O 操作,将数据由临时缓冲区拷贝至 stdin 中。C语言提供的输入输出函数如 scanf 、getchar 等则从上述缓冲区 stdin 中读取数据输入。
scanf 和 getchar 等函数会在 stdin 中读取数据,若上述缓冲区中已存在数据,则直接读取其中的数据,若上述缓冲区为空,则上述函数会挂起,等待数据缓冲的完成( 用户输入回车键或数据缓冲区满后, stdin 会进行数据缓冲,之后上述函数才能继续执行)。 用户一次输入的数据可能会超过 scanf 、getchar 等函数调用所需要的数据,那么所需数据被读取后,剩余的数据仍会存放在缓冲区中,之后的函数调用会直接读取 stdin 中已有的数据。只有当缓冲区为空后,scanf 等函数才会等待用户输入(实际应该是等待 stdin 的缓冲。
scanf函数
scanf函数: scanf C++ reference
函数声明: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 调用仍从上述输入开始读取;
由以上3条规则,通过设置格式化字符串可以规定了 scanf 函数的行为。下面为示例:
scanf("%s,%d",&a,&b); //scanf需先读取一个字符串,再读取一个 ','(规则2),最后读取一个整数
scanf("%d\t%d",&a,&b); //scanf需先读取一个整数,再将格式化字符串中的 '\t' (空白字符)与缓冲区中0个或多个空白字符匹配并清除(规则1),最后读取一个整数
scanf("%d%d",&a,&b); //scanf需要先读取一个整数,之后再读取一个整数,两个整数之间的空白字符会被忽略(规则1)
字符和字符串的读取
对于 stdin 中的字符的读取,scanf 、 getchar 等函数会读取缓冲区中的第一个字符,包括空白字符和非空白字符。
对于 stdin 中的字符串的读取,scanf 会在开始处理后(跳过第一个非空白字符之前的空白字符,规则1)读取到的第一个空白字符处退出,并在读取的字符串尾部加入'\0'作为结束标志。
缓冲区读取数据问题示例
例1:
程序先输出变量未初始化之前的值,再使用scanf读取输入,再显示读取输入之后的值
- printf("%d,%d,%c\n",a,b,c); //输出未初始化之前的值
- scanf("%d%d",&a,&b);
- scanf("%c",&c);
- printf("%d,%d,%c\n",a,b,c); //输出初始化之后的值
结果如下图所示
解释如下:
(1)用户输入至缓冲区中的数据实际为 12 + 空格 + a + 换行符 ;
(2)第一次读取输入时,首先将读取到的第一个数字12赋值给变量 a,之后 scanf 会试图读取下一个十进制数,但是发现下一个非空白字符(忽略输入的空格)为字符 'a',与其所需要读取的数据类型不符,scanf 会退出并返回一个常数值来表示错误信息.此时字符 'a' 并未被读取,仍然存在于缓冲区中;
(3)第二次读取输入时,scanf 就会发现缓冲区中第一个非空白字符为字符 'a',从而会将字符 'a' 赋值给变量 c,并退出。
故而,再次输出变量时,变量 a 和 c 均已改变,而变量 b 只能保持原值。
例2:
用于测试的函数先读取一个字符串,再读取一个字符,并将结果输出
- scanf("%s",a);
- scanf("%c",&b);
- printf("%s,%d",a,(int)b);
输出结果如下
解释如下:
(1)用户在输入时,实际进入缓冲区中的数据为字符串'"abcd" + 换行符;
(2)第一次读取时,scanf 会读取一个字符串,并在遇到第一个空白字符处停止,这里为换行符,即读取的字符串为"abcd",scanf 函数还会在该字符串尾部加入'\0'进行存储;
(3)第二次读取时,scanf 会读取一个字符,进行字符读取时空白符也被视为有效输入字符,故而 scanf 会读取换行符,而换行符的ASCII值即为10;
例3:
将读取输入的要求换一下,要求读取两个字符串
结果scanf会再次等待用户输入
原因在于在读取第一个字符串后,缓冲区中剩余一个换行符,而根据规则1,在读取字符串之前会跳过所有的空白字符,之后scanf会发现此时缓冲区已经为空,从而需要再次等待用户输入。
事实上,对于上述情况,除非第二次读取的参数是可以读取空白字符的 %c,其他的参数均会使得 scanf 认为缓冲区已为空,从而进入等待用户输入的状态。
getchar
getchar 是用于字符输入的C库函数,其函数的声明包含在头文件 stdio.h,函数声明为: int getchar(void).其功能是读取标准输入stdin中的一个字符。
getchar 从标准输入中读取数据,而 stdin 是采用行缓冲的方式记录用户输入,也就是只有当用户键入回车键或输入至缓冲区末尾时,才会开始 I\O 操作,亦即读取一个字符。这样用户可以一次输入不止一个字符,读取过后缓冲区可能不为空。当再次调用 getchar 时,若缓冲区不为空,getchar 就会直接读取在缓冲区中字符,而不是等待用户输入。可以认为是getchar 等待的是行缓冲的完成,而不是用户输入的完成,在行缓冲完成后,只要缓冲区不为空,getchar 就可以读取字符,而不需要等待用户输入。
- /*codeblocks13.12*/
- #include <stdio.h>
- int main(void)
- {
- char ch = '\0';
- while(ch != '\n')
- {
- printf("输入一个字符:");
- ch = getchar();
- printf("\n");
- putchar(ch);
- printf("\n");
- }
- return ;
- }
程序的运行结果如下:
可以明显看到,后续执行中并不要求用户输入,getchar()会直接读取缓冲区中的数据。而且对于字符的读取操作而言,换行符'\n'也被视为一个字符,而不是单纯的结束标志。
等待用户输入的字符输入
getchar 可以直接从缓冲区中读取字符,而不等待用户输入,但这种方式也有可能带来潜在的错误。这里给出两种等待用户输入的字符传入方式。
1.使用 getche 与 getch 函数。上述函数均从键盘上读入一个字节,其中后者不会将字符回显到屏幕上。以这两个函数读取字符时,都是通过调用函数读取一个键盘输入且只有一个。如调用 getche,键盘敲击 'abc' 时,只有一个字符 'a' 会被读取。其他字符为无效输入。但上述函数并不被包含在标准 C 函数库中,需要通过头文件 conio.h 来使用,并不被所有的编译器实现支持。
2.在每次调用 getchar 函数之后,手动对缓冲区进行清除操作。可以使用 fflush() 函数清理缓冲区。C标准规定 fflush()函数可用来刷新输出(stdout)缓冲区(一般是将缓冲区数据写回存储设备)。但对于标准输入(stdin)则没有明确定义。部分编译器定义了 fflush( stdin )的实现,如微软的VC。也就是不同的编译器对于 fflush( stdin )的支持可能不同。GCC编译器没有定义它的实现,所以不能使用 fflush( stdin )来刷新输入缓冲区。
scanf函数读取缓冲区数据的问题的更多相关文章
- PHP file_get_contents函数读取远程数据超时的解决方法
PHP file_get_contents函数读取远程数据超时的解决方法 投稿:junjie 字体:[增加 减小] 类型:转载 这篇文章主要介绍了PHP file_get_contents函数读取 ...
- 关于C中scanf()函数读取字符串的问题
#include <stdio.h> int main(void) { ]; scanf("%s", s_name); printf("Hello, %s!\ ...
- scanf函数与输入缓冲区
本文链接:http://www.cnblogs.com/xxNote/p/4008668.html 今天看书的时候遇到scanf函数与缓冲区的问题,产生了一些猜想即:应该有一个指针来记录缓冲区中读取到 ...
- C/C++ scanf 函数中%s 和%c 的简单差别
首先声明:在键盘中敲入字符后,字符会首先保存在键盘缓冲区中供scanf函数读取(scanf.getchar等函数是读取缓冲区,getch函数是读取的控制台信息,即为直接从键盘读取).另外特别注意键盘上 ...
- scanf函数具体解释与缓冲区
1.基本信息 函数原型: int scanf( char *format, args, ...); 函数返回值: 读入并赋给args的数据个数.遇到文件结束返回EOF,出错返回0. 函数功能: sca ...
- python 读取二进制数据到可变缓冲区中
想直接读取二进制数据到一个可变缓冲区中,而不需要做任何的中间复制操作.或者你想原地修改数据并将它写回到一个文件中去. 为了读取数据到一个可变数组中,使用文件对象的readinto() 方法.比如 im ...
- C语言实现读取字符转换为浮点数,不使用scanf函数
c语言读取int或者float数据,我们习惯于使用scanf函数,但是如果不使用scanf函数,该怎么实现呢. 这里就来尝试一下,不使用scanf来读取数据并转换为float类型. 下面的getflo ...
- C语言scanf函数详细解释
原文链接 函数名: scanf 功 能: 执行格式化输入 用 法: int scanf(char *format[,argument,...]); scanf()函数是通用终端格式化输入函数,它从标准 ...
- scanf()函数用法小结
scanf()函数是格式化输入函数,它从标准输入设备(键盘) 读取输入的信息. 其调用格式为: scanf("<格式化字符串>",<地址表>); ...
随机推荐
- Call to a member function assign() on null
Thinkphp: 在子控制器里面写了一个构造函数,如下 //构造函数 public function __construct(){ echo 1; } 结果页面报错了 ----> Call ...
- CentOS 7 的下载源为aliyun
更换 CentOS 7 的下载源为阿里云 1.备份 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo ...
- Java生成二维码和解析二维码URL
二维码依赖jar包,zxing <!-- 二维码依赖 start --><dependency> <groupId>com.google.zxing</gro ...
- C运算符总结
算术运算符 加减 + - 自左向右 +-同级 乘除取模 * % / 自左向右 高于+ - 自加自减 ++ -- 右结合性 高于基本算术运算符 正负 -+ 自右向左 跟++ --同级 赋值运算符 赋值 ...
- android 9 patch
- [错误记录_C] 还未给指针变量正确赋值的情况下,就使用它的值
错误的代码: 错误的结果: 错误原因分析: 在使用(1) 将pB,pC的值赋给pA的lchild和rchild时: 还未给指针变量pB和pC赋值,现在pB和pC中存的是个垃圾值 Note: (2)- ...
- 八 SocketChannel
SocketChannel是一个连接到Tcp网络套接字的通道.可以通过以下两种方式创建SocketChannel: 1.打开一个SocketChannel并连接到互联网上的某台服务器. 2.一个新连接 ...
- docker 容器中设置 mysql lampp php软链接
在容器中安装xampp后,进入到终端,直接输入mysql php 发现报错,命令未被发现.如果输入/opt/lampp/bin/mysql 就可以进入了,所以我们要找到在容器中安装的位置,然后将他 ...
- linux 安装php扩展swoole redis
本文讲的是已经有redis.so 和swoole.so文件的情况 我的环境是xampp php的扩展目录为 /opt/lampp/lib/php/extensions/no-debug-non-zts ...
- [Java反射基础四]通过反射了解集合泛型的本质
本文接上文"方法反射的基本操作",利用反射了解下java集合中泛型的本质 1.初始化两个集合,一个使用泛型,一个不使用 ArrayList list1 = new ArrayLis ...