当前位置: 代码迷 >> C语言 >> scanf()函数释疑(上/下)
  详细解决方案

scanf()函数释疑(上/下)

热度:714   发布时间:2005-06-22 23:59:00.0
scanf()函数释疑(上/下)

scanf()函数释疑(上)

一、 序言

scanf()函数是所有C语言学习者在学习C语言过程中所遇到的第二个函数(第一个函数是printf(),Brian W.Kerninghan & Dennis M.Ritchie的“hello,world”程序基本上是所有的C语言学习者第一个范例),所以scanf()函数应当是C学习者能熟练运用的一个函数,但有很多初学者对此函数不能很好的运用,在实际编程中错误使用scanf()函数,导至程序产生某种错误不能正常运行,以至产生“scanf()函数有BUG”,“scanf()函数无用论”等等错误观点。 本文结合笔者在编程实践中及论坛上网友所遇到的问题作一释疑,但笔者水平有限(菜鸟级),难免有谬误之处,还望达人指点一二。(Email:knocker.k@126.com) 本文分上,下两篇讲述了C语言中的scanf()函数的用法,重点阐述使用scanf()函数过程中出现的常见错误及对策。当然,文中某些解决方法,均可以采用其他函数和方法来更好地解决,但本文仅限讨论scanf()函数本身。 上篇,详细介绍了scanf()函数控制串的构成。下篇,用实际例程介绍scanf()函数控制串运用出现的常见错误及对策技巧。

二、 scanf()函数的控制串

函数名: scanf 功 能: 执行格式化输入 用 法: int scanf(char *format[,argument,...]);

scanf()函数是通用终端格式化输入函数,它从标准输入设备(键盘) 读取输入的信息。可以读入任何固有类型的数据并自动把数值变换成适当的机内格式。

其调用格式为: scanf("<格式化字符串>",<地址表>);

scanf()函数返回成功赋值的数据项数,出错时则返回EOF。

其控制串由三类字符构成:

1。格式化说明符; 2。空白符; 3。非空白符;

(A) 格式化说明符

格式字符 说明

%a 读入一个浮点值(仅C99有效) %A 同上 %c 读入一个字符 %d 读入十进制整数 %i 读入十进制,八进制,十六进制整数 %o 读入八进制整数 %x 读入十六进制整数 %X 同上 %c 读入一个字符 %s 读入一个字符串 %f 读入一个浮点数 %F 同上 %e 同上 %E 同上 %g 同上 %G 同上 %p 读入一个指针 %u 读入一个无符号十进制整数 %n 至此已读入值的等价字符数 %[] 扫描字符集合 %% 读%符号 附加格式说明字符表

修饰符 说明

L/l 长度修饰符 输入"长"数据 h 长度修饰符 输入"短"数据 W 整型常数 指定输入数据所占宽度 * 星号 空读一个数据 hh,ll同上h,l但仅对C99有效。

(B) 空白字符

空白字符会使scanf()函数在读操作中略去输入中的一个或多个空白字符,空白符可以是space,tab,newline等等,直到第一个非空白符出现为止。

(C) 非空白字符

一个非空白字符会使scanf()函数在读入时剔除掉与这个非空白字符相同的字符。

注:scanf()控制串知识就介绍到这里(应该比较齐全了^_^),如有遗漏下次补上。下面将结合实际例程,一一阐述.

三、 scanf()函数的控制串的使用

例1.

#include "stdio.h" int main(void) { int a,b,c; scanf("%d%d%d",&a,&b,&c); printf("%d,%d,%d\n",a,b,c); return 0; }

运行时按如下方式输入三个值:

3□4□5 ㄌ(输入a,b,c的值)

3,4,5 (printf输出的a,b,c的值)

(1) &a、&b、&c中的&是地址运算符,分别获得这三个变量的内存地址。 (2) "%d%d%d"是按十进值格式输入三个数值。输入时,在两个数据之间可以用一个或多个空格、tab键、回车键分隔。 以下是合法输入方式: ① 3□□4□□□□5ㄌ ② 3ㄌ 4□5ㄌ ③ 3(tab键)4ㄌ 5ㄌ

例2.

#include "stdio.h" int main(void) { int a,b,c;

scanf("%d,%d,%d",&a,&b,&c); printf("%d,%d,%d\n",a,b,c);

return 0; }

运行时按如下方式输入三个值:

3,4,5 ㄌ(输入a,b,c的值)

或者

3,□4,□5 ㄌ(输入a,b,c的值)

3,□□□4,□5 ㄌ(输入a,b,c的值) ...... 都是合法的,但是","一定要跟在数字后面,如: 3□,4,□5 ㄌ就非法了,程序出错。(解决方法与原因后面讲)

再如:

1、sacnf()中的变量必须使用地址。

   int a, b; scanf("%d%d",a,b); //错误 scanf("%d%d",&a,&b);

2、scanf()的格式控制串可以使用其它非空白字符,但在输入时必须输入这些字符。

例: scanf("%d,%d",&a,&b); 输入: 3,4 ㄌ(逗号与"%d,%d"中的逗号对应) scanf("a=%d,b=%d",&a,&b); 输入: a=3,b=4 ㄌ("a=","b=",逗号与"%d,%d"中的"a=","b="及逗号对应)

3、在用"%c"输入时,空格和“转义字符”均作为有效字符。

例: scanf("%c%c%c",&c1,&c2,&c3); 输入:a□b□cㄌ 结果:a→c1,□→c2,b→c3 (其余被丢弃)

scanf()函数接收输入数据时,遇以下情况结束一个数据的输入:(不是结束该scanf函数,scanf函数仅在每一个数据域均有数据,并按回车后结束)。 ① 遇空格、“回车”、“跳格”键。 ② 遇宽度结束。 ③ 遇非法输入。

上篇就写到这里吧,第三小节的例程"抄"自网上的一个教程(原因有二:一,可以少打不少字。二,□ㄌ我不知道怎么打。^_^),并删去其中的错误之处.这里也顺便提醒本文读者一句:凡事要亲力而为,即使是经典的书籍也不免有疏漏之处,所以,用编译器说话是最可靠的,是对是错请编译器告诉你。

scanf()函数释疑(下)

在上篇我已经表达了两个观点,这里再重申一次:1。本文仅对scanf()函数控制串运用进行探讨,本文所有例程并不构成编程建议。2。凡事要亲力而为,不同平台不同编译器,可能会有不同结果。本文所有例程均在WIN-TC+windows Me下调试。

四、 scanf()函数控制串运用出现的常见错误及对策技巧

问题一: 程序编译通过,但运行错误提示如下: scanf : floating point formats not linked Abnormal program termination

出错示例程序:

#include <stdio.h> int main(void) { int i,j ; float s[3][3]; /*这里*/ for(i=0;i<3;i++) for(j=0;j<3;j++) scanf("%f",&s[i][j]);

for(i=0;i<3;i++) for(j=0;j<3;j++) printf("%f",s[i][j]); }

这实际上是个与本文主题无关的问题,也是与scanf()函数无关,是编译器的问题。

原因很明确:没有链接浮点库。早期系统内存资源紧张,多维浮点数组占用内存量大(一维浮点数组就没有此问题),因此TC在编译时尽量不加入无关的部分,在没发现需要浮点转换程序时,就不在可执行程序中安装这个部分。而有时TC又不能正确识别实际上确实需要做浮点转换,因此就会出现上面错误。

解决的方法:告诉TC需要做浮点数的输入转换。将以下语句加入上面程序中标有/*这里*/处。

方法一: float c; scanf("%f",&c);

方法二: float c,*t;//此处手误,现已更正&t===》*t;

t=&c; .....

也就是说,编译器只要有浮点转换的线索,TC就会把浮点转换连上,所以一般大一点的程序里的就会有浮点变量反而没有此问题。 但问题到此并没结束,我在上面有句“一维浮点数组就没有此问题”,那么我们来看看这样行不行:

#include <stdio.h> int main(void) { int i,j ; float s[3][3],*ptr; ptr=&s[0][0]; for(i=0;i<3;i++) for(j=0;j<3;j++) scanf("%f",ptr+i*3+j);

for(i=0;i<3;i++) for(j=0;j<3;j++) printf("%7.2f\n",s[i][j]); }

这样我们就把多维浮点数组降为一维浮点数组来处理,调试一下,程序运行正常。这说明TC编译器仅在处理多维浮点数组(结构体)有此“未链接浮点库”的问题。

问题二:scanf()函数不能正确接受有空格的字符串?如: I love you!

#include <stdio.h> int main() { char str[80]; scanf("%s",str); printf("%s",str);

return 0; }

输入:I live you! 输出:I

scanf()函数接收输入数据时,遇以下情况结束一个数据的输入:(不是结束该scanf函数,scanf函数仅在每一个数据域均有数据,并按回车后结束)。 ① 遇空格、“回车”、“跳格”键。 ② 遇宽度结束。 ③ 遇非法输入。

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

#include <stdio.h> int main() { char str[80]; char str1[80]; char str2[80]; scanf("%s",str);/*此处输入:I love you! */ printf("%s",str); sleep(5);/*这里等待5秒,告诉你程序运行到什么地方*/ scanf("%s",str1);/*这两句无需你再输入,是对键盘盘缓冲区再扫描 */ scanf("%s",str2);/*这两句无需你再输入,是对键盘盘缓冲区再扫描 */ printf("\n%s",str1); printf("\n%s",str2); return 0; }

输入:I love you! 输出:I love you!

好了,原因知道了,那么scanf()函数能不能完成这个任务?回答是:能!别忘了scanf()函数还有一个 %[] 格式控制符(如果对%[]不了解的请查看本文的上篇),请看下面的程序:

#include "stdio.h" int main() { char string[50]; /*scanf("%s",string);不能接收空格符*/ scanf("%[^\n]",string); printf("%s\n",string); return 0; }

问题三:键盘缓冲区残余信息问题

#include <stdio.h> int main() { int a; char c;

do { scanf("%d",&a); scanf("%c",&c); printf("a=%d c=%c\n",a,c); /*printf("c=%d\n",c);*/ }while(c!='N'); }

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()函数之后加个fflush(stdin);,还有加getch(); getchar();也可以,但是要视具体scanf()语句加那个,这里就不分析了,读者自己去摸索吧。但是加fflush(stdin);不管什么情况都可行。

函数名: fflush 功 能: 清除一个流 用 法: int fflush(FILE *stream);

#include <stdio.h> int main() { int a; char c;

do { scanf("%d",&a); fflush(stdin); scanf("%c",&c); fflush(stdin); printf("a=%d c=%c\n",a,c);

}while(c!='N'); }

这里再给一个用“空格符”来处理缓冲区残余信息的示例:

运行出错的程序:

#include <stdio.h> int main() { int i; char j; for(i = 0;i < 10;i++) { scanf("%c",&j);/*这里%前没有空格*/ } }

使用了空格控制符后:

#include <stdio.h> int main() { int i; char j; for(i = 0;i < 10;i++) { scanf(" %c",&j);/*注意这里%前有个空格*/ } }

可以运行看看两个程序有什么不同。

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

#include <stdio.h> int main() { int a,b,c; /*计算a+b*/

scanf("%d,%d",&a,&b); c=a+b; printf("%d+%d=%d",a,b,c); }

如上程序,如果正确输入a,b的值,那么没什么问题,但是,你不能保证使用者每一次都能正确输入,一旦输入了错误的类型,你的程序不是死锁,就是得到一个错误的结果,呵呵,这可能所有人都遇到过的问题吧?

解决方法:scanf()函数执行成功时的返回值是成功读取的变量数,也就是说,你这个scanf()函数有几个变量,如果scanf()函数全部正常读取,它就返回几。但这里还要注意另一个问题,如果输入了非法数据,键盘缓冲区就可能还个有残余信息问题。

正确的例程:

#include <stdio.h> int main() { int a,b,c; /*计算a+b*/

while(scanf("%d,%d",&a,&b)!=2)fflush(stdin); c=a+b; printf("%d+%d=%d",a,b,c); }

就此结束此文吧,最后还得照例谦虚几句,本人水平有限(的的确确有限^_^,这到是真话),谬误难免还望达人指点一二,在下在此谢过了. (全文完)

knocker 2004.10.21

搜索更多相关的解决方案: 函数  scanf  释疑  C语言  学习者  

----------------解决方案--------------------------------------------------------
虽然我没遇到过此类问题,但还是要说,写得不错!!!!鼓励!!
----------------解决方案--------------------------------------------------------
knocker这孩子几天不见长进了~呵呵这么好的文章怎么不卖钱啊?写得不错,特别是关于缓冲区的问题,我都不知道呢
----------------解决方案--------------------------------------------------------
非常好,顶!!!!!!!!!
----------------解决方案--------------------------------------------------------
tempnetbar老大,要夸我也不是这么夸的吧????看看日期!是有人找不到我重发的。
----------------解决方案--------------------------------------------------------

不得不说,fflush(stdin);是错的。fflush是用来清空标准输出的,fflush(stdin);在C语言中没有定义,是依赖编译器的,不可移植的。

an extract from the C standard will help explain:

int fflush(FILE *ostream);

ostream 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.

On occasions you may need to clear unwanted data in an input stream, most commonly keyboard input. This may be after a call to a read function that failed to input all available data, or it may be to ensure that the user doesn't try the "type ahead" approach when using your application.

As far as standard C and C++ go, there is no guaranteed method to clear an input stream. You can write code that will do a reasonably good job, but it probably won't work in all instances your require it to. Why not? Because in standard C/C++ input streams are buffered. This means that when you hit a key on the keyboard, it isn't sent directly to your program. Instead it is buffered by the operating system until such time that it is given to your program. The most common event for triggering this input is the pressing of the [Enter] key.

If you are sure that unwanted data is in the input stream, you can use some of the following code snippets to remove them. However, if you call these when there is no data in the input stream, the program will wait until there is, which gives you undesirable results.

/* * This C implementation will clear the input buffer. * The chances are that the buffer will already be empty, * so the program will wait until you press [Enter]. */

#include <stdio.h>

int main(void) { int ch; char buf[BUFSIZ]; puts("Flushing input"); while ((ch = getchar()) != '\n' && ch != EOF); printf ("Enter some text: "); if (fgets(buf, sizeof(buf), stdin)) { printf ("You entered: %s", buf); } return 0; }

/* * Program output: * Flushing input blah blah blah blah Enter some text: hello there You entered: hello there * */

And a C++ version:

#include <iostream> #include <cstdio>

using std::cin; using std::cout; using std::endl;

int main(void) { int ch; char buf[BUFSIZ]; cout <<"Flushing input" <<endl; while ((ch = cin.get()) != '\n' && ch != EOF); cout <<"Enter some text: "; cout.flush(); if (cin.getline(buf, sizeof(buf))) { cout <<"You entered: " <<buf <<endl; }

return 0; }

/* * Program output: * Flushing input blah blah blah blah Enter some text: hello there You entered: hello there * */

But what about fflush(stdin); Some people suggest that you use fflush(stdin) to clear unwanted data from an input buffer. This is incorrect.

Why fflush(stdin) is wrong On occasions you may need to clear unwanted data in an input stream, most commonly keyboard input. One frequently suggested way of doing this is by using fflush(stdin). This is incorrect, and should be avoided, here is why.

stdin is a standard FILE* variable that points to the input stream normally used for keyboard input. The fflush() function is deemed to flush buffers. Put the two together and you have a method for clearing the input stream easily, right? WRONG! This is a common misconception in C and C++ programming, an extract from the C standard will help explain:

int fflush(FILE *ostream);

ostream 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.

So, if the file stream is for input use, as stdin is, the behaviour is undefined, therefore it is not acceptable to use fflush() for clearing keyboard input. As usual, there are some exceptions, check your compiler's documentation to see if it has a (non-portable) method for flushing input.


----------------解决方案--------------------------------------------------------
knocker,买个救生圈置顶一下!
----------------解决方案--------------------------------------------------------
以下是引用Antigloss在2005-6-23 13:13:29的发言: …… ……

But what about fflush(stdin); Some people suggest that you use fflush(stdin) to clear unwanted data from an input buffer. This is incorrect.

Why fflush(stdin) is wrong On occasions you may need to clear unwanted data in an input stream, most commonly keyboard input. One frequently suggested way of doing this is by using fflush(stdin). This is incorrect, and should be avoided, here is why.

stdin is a standard FILE* variable that points to the input stream normally used for keyboard input. The fflush() function is deemed to flush buffers. Put the two together and you have a method for clearing the input stream easily, right? WRONG! This is a common misconception in C and C++ programming, an extract from the C standard will help explain:

int fflush(FILE *ostream);

ostream 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.

So, if the file stream is for input use, as stdin is, the behaviour is undefined, therefore it is not acceptable to use fflush() for clearing keyboard input. As usual, there are some exceptions, check your compiler's documentation to see if it has a (non-portable) method for flushing input.

用E文书来压knocker哈
----------------解决方案--------------------------------------------------------
Antigloss是对的。谢谢纠正!希望下次写中文,这个几洋字让我费了不少时间解读 -^- 不过,fflush(FILE *ostream)能清输入缓冲区,当ostream关联文件是“读打开”状态。 还有一点,我再次申明一下,我要上面已经再三说到了: [QUOTE]在上篇我已经表达了两个观点,这里再重申一次:1。本文仅对scanf()函数控制串运用进行探讨,本文所有例程并不构成编程建议。2。凡事要亲力而为,不同平台不同编译器,可能会有不同结果。本文所有例程均在WIN-TC+windows Me下调试。[/QUOTE]
----------------解决方案--------------------------------------------------------
fflush(stdin);到底是什么呢:
有些人认为fflush(stdin)是用来清除在输入缓冲中你不想要的数据。但这样的认为是错的!
那fflush(stdin)为什么错呢?
有时你免不了需要清除一下那些在输入流中你不想要的数据,那些数据大多数情况是由键盘输入的。通常很习惯性的方法就是用fflush(stdin)来进行清除。但这样是错误的,而且应该避免,下面就是理由。(外国人怎么写文章比中国人还会托……烦死了)
stdin是一个标准FILE*(文件型指针)指向通常是用键盘的输入的输入流。fflush()的功能一般是被视为清除缓冲区的。一起输入两个,然后你有办法来清除这些输入流,不是么?错!这是一个对C/C++编程很菜鸟式的概念性错误认识!下面引用自C标准来帮助解释:
int fflush(FILE *ostream);
ostream流指向一个输出的流或一个在不久以前曾经操作过更(第四音)新的流,fflush的功能会导致任何并没有被写入缓冲流的数据被分配给主机环境,然后这些数据被写入文件;换句话,这种方式是没被定义的!(我狂晕啊,老外写的句子这么麻烦)
So,如果文件流是给输入用的,就像stdin一样,那么这种方式是没被定义的,因此用fflush()来清除键盘输入的这种方法是不能接受的。
通常,应该还有其他办法,查看一下你的协议文件是否有一种(非移植性--作者好像是想说非移植性指针)方法来清除输入。


老外的书,只能看其大概意思,具体一些鸟词,翻译不出来
或者说只可意会,不可言传---(一般菜鸟都这样说)

[此贴子已经被作者于2005-6-23 15:18:35编辑过]



----------------解决方案--------------------------------------------------------
  相关解决方案