scanf函数

与printf函数一样,都被定义在头文件stdio.h里,因此在使用scanf函数时要加上#include <stdio.h>。它是格式输入函数,即按用户指定的格式从键盘上把数据输入到指定的变量之中。



函数原型:   int scanf(const char *format,...);

函数 scanf() 是从标准输入流stdio (标准输入设备,一般是键盘)中读内容的通用子程序,
可以根据说明的格式读入若干个数据,并保存在对应地址的变量中。
其调用形式为: scanf("<格式说明字符串>",<变量地址列表>);变量地址要求有效,并且与格式说明的次序一致。


scanf()函数返回成功赋值的数据项数,读到文件末尾出错时则返回EOF。



​例如 scanf​​​​(​​​​"%d %d"​​​​,&a,&b);​


如果a和b都被成功读入,那么scanf的返回值就是2

如果只有a被成功读入,返回值为1

如果a和b都未被成功读入,返回值为0

如果遇到错误或遇到end of file,返回值为EOF。

且返回值为int型.

例:使用scanf函数输入数据。



#include<stdio.h>
int main(void)
{
int a,b,c;
printf("输入a,b,c\n");
scanf("%d%d%d",&a,&b,&c);
printf("a=%d,b=%d,c=%d\n",a,b,c);
fflush(stdin);
return 0;
}


&a,&b,&c中的&是地址运算符,&a指a在内存中的地址。scanf的作用是:按照a,b,c的内存地址将输入的数据存到a,b,c中去。变量a,b,c的地址是在编译连续阶段分配的(存储顺序由编译器决定)。

这里注意:如果scanf中%d是连着写的如“%d%d%d”,在输入数据时,数据之间不可以加逗号,只能是空格或tab键或者回车键——“2 3 4” 或 “2(按tab)3(按tab)4(按tab)”。若是“%d,%d,%d”,则在输入数据时需要加“,”,如“2,3,4”.

问题一


如何让scanf()函数正确接受有空格的字符串?如: I love you!



1 #include<stdio.h>
2 int main(void)
3 {
4 char str[80];
5 scanf("%s",str);
6 printf("%s",str);
7 return 0;
8 }


输入:​​I love you!​

上述程序并不能达到预期目的,scanf()扫描到"I"后面的空格就认为对str的赋值结束,并忽略后面的"love you!".这里要注意是"love you!"还在键盘缓冲区(关于这个问题,网上我所见的说法都是如此,但是,我经过调试发现,其实这时缓冲区字符串首尾指针已经相等了,也就是说缓冲区清空了,scanf()函数应该只是扫描stdin流,这个残存信息是在stdin中)。我们改动一下上面的程序来验证一下:



1 #include<stdio.h>
2 #include<windows.h>
3 int main(void)
4 {
5 char str[80],str1[80],str2[80];
6 scanf("%s",str);/*此处输入:Iloveyou!*/
7 printf("%s\n",str);
8 Sleep(5000);/*这里等待5秒,告诉你程序运行到什么地方*/
9 /*
10 不是sleep(5)
11 1,函数名是Sleep不是sleep。
12 2,C/C++中,unsignedSleep(unsigned)应该是毫秒ms.
13 */
14 scanf("%s",str1);/*这两句无需你再输入,是对stdin流再扫描*/
15 scanf("%s",str2);/*这两句无需你再输入,是对stdin流再扫描*/
16 printf("%s\n",str1);
17 printf("%s\n",str2);
18 return 0;
19 }


输入:​​I love you!​

输出:



​I​


​love​


​you!​


好了,原因知道了,所以结论是:残留的信息 love you是存在于stdin流中,而不是在键盘​​缓冲区​​中。那么scanf()函数能不能完成这个任务?回答是:能!别忘了scanf()函数还有一个 %[] 格式控制符(如果对%[]不了解的请查看本文的上篇),请看下面的程序:



1 #include<stdio.h>
2 int main(void)
3 {
4 char str[50];
5 scanf("%[^\n]",str);/*scanf("%s",string);不能接收空格符*/
6 printf("%s\n",str);
7 return 0;
8 }


问题二


键盘缓冲区残余信息问题



1 #include<stdio.h>
2 int main(void)
3 {
4 int a;
5 char c;
6 while(c!='N')
7 {
8 scanf("%d",&a);
9 scanf("%c",&c);
10 printf("a=%dc=%c\n",a,c);/*printf("c=%d\n",c);*/
11 }
12 return 0;
13 }


scanf("%c", &c);这句不能正常接收字符,什么原因呢?我们用printf("c = %d\n", c);将C用int表示出来,启用printf("c = %d\n", c);这一句,看看scanf()函数赋给C到底是什么,结果是c=10 ,ASCII值为10是什么?换行即\n.对了,我们每击打一下"Enter"键,向键盘缓冲区发去一个“回车”(\r),一个“换行"(\n),在这里\r被scanf()函数处理掉了(姑且这么认为吧^_^),而\n被scanf()函数“错误”地赋给了c.解决办法:可以在两个scanf()函数之后加getchar()。

也可以使用fflush函数。fflush()函数冲洗流中的信息,该函数通常用于处理磁盘文件。



1 #include<stdio.h>
2 int main(void)
3 {
4 int a;
5 char c;
6 while(c!='N')
7 {
8 scanf("%d",&a);
9 fflush(stdin);
10 scanf("%c",&c);
11 fflush(stdin);
12 printf("a=%dc=%c\n",a,c);
13 }
14 return 0;
15 }


问题三


输入类型与格式化字符串不匹配导致stdin流的阻塞。



1 #include<stdio.h>
2 int main(void)
3 {
4 int a=0,b=0,c=0,ret=0;
5 ret=scanf("%d%d%d",&a,&b,&c);
6 printf("第一次读入数量:%d\n",ret);
7 ret=scanf("%d%d%d",&a,&b,&c);
8 printf("第二次读入数量:%d\n",ret);
9 return 0;
10 }


我们定义了a,b,c三个变量来接受输入的内容,定义了变量ret来接收scanf函数的返回值。


正确输入的话:


scanf函数_#include


但是当输入内容与格式换字符串不匹配时,结果会令人大跌眼镜。

scanf函数_#include_02


执行到第一个scanf时,当输入字符’b’的时候与 ret=scanf("%d%d%d",&a,&b,&c);中的格式化字符串不匹配,stdin流被阻塞,scanf函数不在 读取后面的部分,直接将1返回,表示只将stdin流中的1读入到了变量a中。


执行到第二个scanf时,字符’b’还是与格式化字符串不匹配,stdin流仍然被阻塞,所以没有提示输入,scanf函数将0返回。


将代码作如下修改,可以有力的证明上述结论。



1 #include<stdio.h>
2 int main(void)
3 {
4 int a=0,b=0,c=0,ret=0;
5 ret=scanf("%d%d%d",&a,&b,&c);
6 printf("第一次读入数量:%d\n",ret);
7 ret=scanf("%c%d%d",&a,&b,&c);
8 printf("第二次读入数量:%d\n",ret);
9 return 0;
10 }


当把第二个scanf函数内的格式化字符串改为”%c%d%d”时,运行结果如下

scanf函数_输入流_03


执行到第一个scanf函数时,由于输入’b’的原因scanf函数直接返回1,stdin流阻塞。


执行到第二个scanf函数时,字符’d’与格式化字符串”%c%d%d”中的%c匹配,stdin流终于疏通,在输入6,则将变量a,b,c分别赋值为98(‘b’的ASCII码)、2、6,scanf函数返回3。


由上述问题可知,当使用scanf函数时,如果遇到一些匪夷所思的问题,在scanf函数后正确使用fflush(stdin);,清空输入缓冲区,可以解决很多问题。以本题为例:



1 #include<stdio.h>
2 int main(void)
3 {
4 int a=0,b=0,c=0,ret=0;
5 ret=scanf("%d%d%d",&a,&b,&c);
6 fflush(stdin);
7 printf("第一次读入数量:%d\n",ret);
8 ret=scanf("%d%d%d",&a,&b,&c);
9 fflush(stdin);
10 printf("第二次读入数量:%d\n",ret);
11 return 0;
12 }


运行结果:

scanf函数_数据_04

 

问题四


如何处理scanf()函数误输入造成程序死锁或出错



1 #include<stdio.h>
2 int main(void)
3 {
4 int a,b,c;
5 scanf("%d,%d",&a,&b);
6 c=a+b;/*计算a+b*/
7 printf("%d+%d=%d",a,b,c);
8 return 0;
9 }


如上程序,如果正确输入a,b的值,那么没什么问题,但是,你不能保证使用者每一次都能正确输入,一旦输入了错误的类型,你的程序不是死锁,就是得到一个 错误的结果,呵呵,这可能所有人都遇到过的问题吧?解决方法:scanf()函数执行成功时的返回值是成功读取的变量数,也就是说,你这个scanf() 函数有几个变量,如果scanf()函数全部正常读取,它就返回几。但这里还要注意另一个问题,如果输入了非法数据,键盘缓冲区就可能还个有残余信息问题。正确的例程:



1 #include<stdio.h>
2 int main(void)
3 {
4 int a,b,c;
5 while(scanf("%d%d",&a,&b)!=2)
6 fflush(stdin);
7 c=a+b;
8 printf("%d+%d=%d",a,b,c);
9 return 0;
10 }


补充


fflush(stdin)这个方法在GCC下不可用。(在VC6.0下可以)


以下是 C99 对 fflush 函数的定义:


int fflush(FILE *stream);


如果stream指向输出流或者更新流(update stream),并且这个更新流


执行的操作不是输入,那么fflush函数将把任何未被写入的数据写入stream


指向的文件(如标准输出文件stdout)。否则,fflush函数的行为是不确定的。


C和C++的标准里从来没有定义过 fflush(stdin)。


fflush(NULL)清空所有输出流和上面提到的更新流。如果发生写错误,fflush


函数会给那些流打上错误标记,并且返回EOF,否则返回0。


由此可知,如果 stream 指向输入流(如 stdin),那么 fflush 函数的行为是不确定的。故而使用


fflush(stdin) 是不正确的,至少是移植性不好的。


可采用如下方法:


方法一:



1 /*此函数可以和scanf函数一起使用,但使用%c输入时要注意,即此函数只能用于缓冲区非空的情况*/
2 #include<stdio.h>
3 void flush()
4 {
5 charc;
6 while((c=getchar())!='\n'&&c!=EOF);
7 }
8 intmain(void)
9 {
10 int a,b,c;/*计算a+b*/
11 while(scanf("%d%d",&a,&b)!=2)
12 flush();
13 c=a+b;
14 printf("%d+%d=%d",a,b,c);
15 return 0;
16 }


 


方法二:


使用getchar()代替fflush(stdin)


程序示例:



1 #include<stdio.h>
2 int main(void)
3 {
4 int i,c;
5 while(1)
6 {
7 printf("Pleaseinputaninteger:");
8 scanf("%d",&i);
9 if(feof(stdin)||ferror(stdin))
10 {
11 //如果用户输入文件结束标志(或文件已被读完),或者发生读写错误,则退出循环
12 //dosomething
13 break;
14 }
15 //没有发生错误,清空输入流。通过while循环把输入流中的余留数据“吃”掉
16 while((c=getchar())!='\n'&&c!=EOF);
17 //可直接将这句代码当成fflush(stdin)的替代,直接运行可清除输入缓存流
18 //使用scanf("%*[^\n]");也可以清空输入流,不过会残留\n字符。
19 printf("%d\n",i);
20 }
21 return 0;
22 }