阻挡我们学习c的绊脚石(1)之scanf函数
基于我在接触c语言的一些经历以及鱼c论坛中的一些萌新鱼油的问题,我还是想分享一下关于scanf输入函数的一些常见的应用技巧和常见问题,希望能给萌新鱼油一点启示,以起到抛砖引玉的作用。如有错误,还请各路大神勘误。我们在c中接触scanf非常早,常在学习c语言基本语法中使用它,来调试一些小程序。但是,使用它总是会出现莫名其妙的问题,导致小程序的输出完全跑偏。这就会给我们带来困扰,总会以为是程序的其他部分出现问题(因为scanf是人家给的呀,怎么会错呢),从而打击了新鱼油的自信。那么,今天我们就来看看,scanf它到底有多神秘。
首先,来看一下 scanf的函数原型
int scanf( const char *format [,argument]... );
msdn 给的功能介绍是从标准的输入流中读取格式化的数据。我们应该注意的就应该是什么是格式化数据,以及输入流是什么。 int类型的返回值,就是读取正确并且分配给对应变量的格式化数据个数或者是一个EOF标记,本贴暂不讨论文件相关,所以不探讨EOF。 第一个参数是一串格式串,就是我们常用的双引号里的东西,之后的参数叫做可变参数列表,就是我们熟悉的那些变量的地址了。
然后,我们从函数的第一个参数开始看起。这是因为,这里直接决定了我们的scanf函数能否正确把数据分配给变量。下面是scanf支持的格式串。
c 如%c或%1c读入单字符,%2c读入两个字符(后面不会加上空字节),以此类推。
s 读入一个的字符序列,后面会加上空字节。
d, u 读入可选有符号(可选有符号表示输入时可以带符号也可以不带符号,不带符号则视为非负)十进制整数
i 读入可选有符号整数
a, e, f, g, A, E, F, G 读入可选有符号浮点数
o 读入可选有符号八进制整数
x, X 读入可选有符号十六进制整数
p 读入一个指针值
n 不读入任何字符,而是把到该位置已读入的字符数存储到与之对应的int*指向的位置。本转换说明符如果带有*或者带有域宽信息(如:%*n或%3n等),则后果是未定义的
% 读入% 符号(百分号)
* 表示读入的数据将被舍弃。带有*的格式指令不对应可变参数列表中的任何数据。可与其他格式混用 如 %*c
最值得我们注意的是,除以上的格式串之外,其他的格式串均会引起未定义的行为,也就是scanf函数不认识这些格式串,就不会从输入流中取出数据,那么数据就会停留在这个输入缓冲区内,等待下一个读数据的函数到来。(注:以上的格式串不必全部记住,只记住常用即可,但是要注意不是以上说明的格式串就是不合法的,不会被scanf认识。所以使用时不能乱编,如%.2f就是典型的乱写。)
最后来看,可选参数列表,就是一堆指针,必须与前面的格式串一一对应。此处我们刚接触的时候会容易出错,把地址写成变量名,而熟悉之后并不容易出错。最值得注意的就是数组名是可以直接写的,因为数组名就是数组的地址。
显然,scanf函数已经说完了。但是,我们的目的还没有达到,接着我们来说说一些错误和技巧。
错误一:乱写格式串,就像之前说的%.2f就不可以。格式串中有域宽说明符,但是它是以一个非零的十进制整数形式出现。表示该格式指令最多读入的字符数。如
char c;
scanf("%5c", c);
printf("%s", c);//输出结果只会是输入的前五位其余为乱码
int i;
scanf("%5d", &i);
printf("%d", i);//输出只会是输入的前五位,少于5位按输入输出
错误二:把可变参数列表搞错,此处必须要是存储位置的指针。此处熟练之后基本不会出错。
错误三:格式说明符之间的符号。格式串之间加空格,制表符、新行符会被忽略,因为这三者能用来分割数据项,而其他非空格字符则要求被匹配,说明如下。
scanf("%d\n%d", &i, &j);
printf("%d, %d", i, j);
scanf("%d\t%d", &i, &j);
printf("%d, %d", i, j);
scanf("%d %d", &i, &j);
printf("%d, %d", i, j);
这三者等价
scanf("%d\\%d", &i, &j);
printf("%d, %d", i, j); // 此处就要求输入的是 如1\2
scanf("%dt%d", &i, &j);
printf("%d, %d", i, j); // 此处要求输入1t2
scanf("%d,%d", &i, &j);
printf("%d, %d", i, j);//此处就要求输入1,2
scanf("%d%%%d", &i, &j);
printf("%d, %d", i, j);// 此处就要求输入1%2
这样才能全部被输入。这就告诉我们,scanf不同于printf,格式转换符之间不能随便加符号。除非有特殊需要。
再说一说常用的技巧。说是技巧有点太高估自己了,但是有自信毕竟是好事不是吗。(☆_☆)/~~
技巧一:利用scanf函数的返回值。这个函数的返回值是经常被大家所忽略的,虽然小甲鱼的课程中涉及过。根据返回值,可以判断scanf函数是否按照我们的要求工作,直接上代码。
int year, month, day, status;
printf("请按照年-月-日格式输入要算的生辰八字:");
status = scanf("%4d-%2d-%2d", &year, &month, &day);
if (3 == status)
{
printf("%d-%d-%d", year, month, day);
}
else
printf("您没按要求输入,失去了算卦的资格\n");
这个例子,综合了我们之前说的一些知识点呢O(∩_∩)O。
技巧二:利用*舍弃回车符 。scanf中处理字符出现的很多问题都是由回车符导致,因为我们确定数据时是按回车键,而恰巧scanf只读入对应的格式说明符对应字符,而回车键就留在缓冲区内,从而可能影响下面输入。如:
char a,b;
a = b = 'z';
scanf("%c", &a);
printf("i = %c", a);
scanf("%c", &b);
printf("b = %c", b);//这给一个输入1给到a,回车,则输出为a = 1b= 换行
而将 scanf("%c", &a);改为scanf("%c%*c"); 则结果不同
技巧三:利用字符处理函数getchar()等函数,捕获回车。同上个问题。我们这样做。
a = b = 'z';
scanf("%c", &a);
printf("i = %c", a);
getchar();
scanf("%c", &b);
printf("b = %c", b);
则也能让用户为b赋值。避免小错误。
技巧四:暴力法(fflush函数),直接清除输入缓冲,这个就不知针对字符处理函数,可以用来调试,屏蔽模块之间的输入影响。如:
a = b = 'z';
scanf("%c", &a);
printf("i = %c", a);
fflush(stdin);
scanf("%c", &b);
printf("b = %c", b);
注:fflush在vc6以上可能不可用;
好了。今天的篇幅也不小了,本来还想说一说缓冲区的前世今生,以及输入输出流和缓冲区的关系(本次是模糊了两者之间的概念的)。都放到下次的帖子来说吧。哇,第一次 发帖思维都有些混乱了。下期预告
getchar() 有缓冲区 stdio.h 有回显
getch() 无缓冲区 conio.h 无回显
getche() 无缓冲区 conio.h 有回显,是不是看着有点烦 。好了我们下次再见喽。拜拜n(*≧▽≦*)n
有大手子知道相应的更好的使用技巧,欢迎添加哦{:5_96:}
页:
[1]