scanf函数

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时,当输入字符’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函数时,由于输入’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()函数误输入造成程序死锁或出错
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 }

 

posted on 2015-12-18 18:46  华山青竹  阅读(800)  评论(0编辑  收藏  举报

导航