函数名: 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()函数在读入时剔除掉与这个非空白字符相同的字符
代码如下 |
复制代码 |
#include <stdio.h>
#include <stdarg.h>
#include "loc_incl.h"
int scanf(const char *format, ...)
{
va_list ap;
int retval;
va_start(ap, format);
retval = _doscan(stdin, format, ap);
va_end(ap);
return retval;
}
|
对于va_list、va_start、va_end等在stdarg.h头文件中定义的宏,都已经在(stdarg.h头文件源代码分析)一文中介绍过。
在上述代码中我们可以看到有一个_doscan函数,而这一函数在头文件loc_incl.h中定义,函数声明如下:
int _doscan(FILE * stream, const char *format, va_list ap);
_doscan函数的实现源代码如下:
_doscan函数代码
在上面的源代码中,值得注意的是第26行的getc宏,定义代码如下:
#define getc(p) (--(p)->_count >= 0 ? (int) (*(p)->_ptr++) :
__fillbuf(p))
getc的调用形式:ch=getc(fp); 功能是从文件指针指向的文件读入一个字符,并把它作为函数值返回给int型变量ch。
第4行~第17行,定义一些后面需要用到的变量
第23行~第34行,跳过format格式串中的空格,并且跳过输入流中的空格
第37行~第42行,输入流stream与format格式串中的空白符(空白符可以是空格(space)、制表符(tab)和新行符(newline))保持一致
第44行~第52行,在format中的字符为'%'的前提下,stream中的字符也为'%',则继续
第54行~第57行,format当前字符为'*',表示读指定类型的数据但不保存
第58行~第62行,指定说明最大域宽。 在百分号(%)与格式码之间的整数用于限制从对应域读入的最大字符数于宽度
第64行~第282行,switch语句,用于格式修饰符,这些修饰符包括: h、l、L、c、p、b、d、i、o、u……,还有基于扫描集的'['修饰符
对scanf函数的源码分析,需要在scanf函数的语法格式详细的理解基础上进行,由于scanf函数实现十分复杂,需要仔细的品味,这里只是比较初步的分析,具体还有待后期不断的完善
格式化错误输入问题,例子
代码如下 |
复制代码 |
#include <stdio .h>
int main()
{
int i;
do
{
scanf("%d",&i);
switch(i)
{
case 1:printf("1n");break;
case 2:printf("2n");break;
case 3:printf("3n");break;
default:printf("othern");
}
}while(1)
return 0;
}
</stdio>
|
看起来就是每次输入一个整数,判断属于那种情况,再简单不过了。我们往往关注的是正确情况下的结果,但是如果我们错误的输入了,结果还会和预想的一样么?我先输了一次1,其他正确情况都判断过。然后输入了一个a。此时程序进入了死循环,重复输出1。什么原因呢?
我首先做了一个断点调试,发现在后来的几次输入中,scanf并没有把a的值赋值给i,也就是i的结果没有被覆盖。但是为什么我没有输入也会继续执行呢?这里肯定是sacnf对缓冲区的操作问题。知道问题的地方,scanf对错误的处理具体还是不知道。后来查了下,具体解释如下:
1、scanf对于正确的输入,会继续判断后面的是否符合格式输入,不是的话过滤掉,遇到回车就结束,然后把正确的字符从缓冲区取走,但是回车不取。
2、对于第一个就是错误的格式。如果是回车的话,会从缓冲区取走,但是直接丢弃。如果遇到不符合格式的,scanf就直接结束。
所以,对于该问题,后来缓冲区中的序列如下:n a n ,遇到n,取走,继续,遇到a,scanf结束。从此就是中被这个a给挡下来了,也就出现了死循环的问题。 |