黑马程序员技术交流社区

标题: 对scanf函数的一个总结 [打印本页]

作者: fantacyleo    时间: 2014-7-2 22:59
标题: 对scanf函数的一个总结
scanf函数是C编程中常用的读取用户键盘输入的函数,也是一个让大家比较头疼的函数。看到有人提问是否能总结一下这个函数,我就试着结合C99标准把这个函数需要注意的地方说一下。不过,对iOS开发来说,C只是打个基础,iOS App中不会用scanf来接收用户输入,大家不用太深究。

首先,scanf是C标准库中的一个函数,这就意味着这个函数的功能和行为是由C标准规定的。C99标准中对这个函数的声明如下:
  1. #include <stdio.h>
  2. int scanf(const char * restrict format, ...);
复制代码
一、scanf函数是在stdio.h这个头文件中声明的。C语言“很小”,语言本身连最基本的输入输出功能都不提供,全部放到库函数中实现。也正因为小巧玲珑,C的适应性极强,应用范围极广。

二、scanf函数有返回值,类型是int,它等于scanf成功匹配并读入的元素个数。可以利用这个返回值来校验是否达到了预期的读取目标。比如打算让用户输入3个整数,结果一看scanf返回值只有2,那说明用户未按要求输入,可以编写相应代码要求其重新输入。

三、大家最关心的部分来了,那就是scanf的形参部分“const char * restrict format, ...”。通常,我们会这样调用scanf函数:
  1. int a, b;
  2. scanf("%d %d", &a, &b);
复制代码
上面代码中的"%d %d"就是scanf形参的“const char * restrict format"部分,又叫格式化字符串,而"&a, &b"则是scanf形参中的"..."部分。"..."是C语言中的特殊语法,叫可变参数,没必要深究,大意就是可以传入任意数目的参数。不过,编程中一定要确保格式化字符串中的"%"个数和可变参数个数相等,如果%多了,C标准说我也不知道怎么办,编译器你们随意吧;如果可变参数多了,多出来的参数就不会被赋值。

格式化字符串的%后面可以跟哪些东西?C标准列了挺多,当然我们只要知道常用的几个:%s(匹配字符串), %d(匹配有符号整数), %c(匹配单个字符), %u(匹配无符号整数), %f(匹配单精度浮点数), %x(匹配十六进制整数), %p(匹配指针)。由于整数有不同长度,浮点数区分单精度双精度,因此又加上了长度标识符l和ll,衍生出:%ld(匹配long), %lld(匹配long long), %lu(匹配unsigned long), %llu(匹配unsigned long long), %lf(匹配double)。顺便提一下有的OC代码中会出现%i,%i和%d一样也匹配整数,但%i不仅能匹配10进制整数,还能匹配16进制和8进制整数,比如你输入0x1a,%i就能识别并将其转为对应的十进制数26.

可变参数必须是指针,用C标准的话说,scanf会把可变参数当作指针,将成功读取的元素存放到可变参数所指向的内存空间中(using subsequent arguments as pointers to the objects to receive the converted input) 初学者最容易犯的错误就是少写了取地址运算符&,结果往往就是程序运行时收到一条著名的错误:segmentation fault

最后就是格式化字符串如何匹配键盘输入的内容。基本规则是:
scanf会按照格式化字符串的内容来匹配你的输入,普通字符匹配输入的普通字符,%则按其含义匹配输入的字符。如果匹配、读取成功,则继续匹配、读取剩余字符串,否则函数就返回(If a directive fails,  the function returns)。


根据这个基本规则,喜欢在格式化字符串中包含提示信息的人要当心了。比如写scanf("请输入两个整数:%d %d", &a, &b); 按照基本规则,输入者惨了,在他输入两个整数之前,他必须原封不动地输入"请输入两个整数:"。如果你直接输入"123 456",那么scanf发现"1"不匹配"请",scanf函数将立即返回,导致a和b没有被赋值。所以尽量不要在scanf的格式化字符串中放非%开头的普通字符,要提示用户输入,可以在scanf之前加一条printf。


举一个例子说明基本规则:
  1. scanf("%d%d%f%f", &a, &b, &x, &y);
复制代码

假设用户输入:
  1. 1-20.3k
复制代码
那么scanf首先匹配%d,它看到1符合%d的要求,就继续看下一个字符。由于整数中间不能出现减号,那么此次匹配结束,成功读取整数1,赋值给a,而减号则放回一个缓冲区中,供下一次读取。由于上一次匹配%d成功,因而scanf不会返回,继续匹配第二个%d。发现以减号开头,可以视为负数,符合整数要求,于是继续读入2和0。由于整数不能出现小数点,因而此次匹配结束,成功读取-20赋值给b,小数点放回缓冲区,接下来匹配%f。浮点数可以省略0,直接以小数点开头,因此读取浮点数0.3,浮点数中不能出现字母k,因此匹配结束,x赋值0.3,k放回缓冲区,继续匹配%f。浮点数中不能包含k,因此匹配失败,scanf返回,k放回缓冲区,供下一次scanf读取。


还有两条对基本规则的补充:
1. 除了%c,scanf在寻找读取数据的起始位置时,会跳过输入字符串中的所有空白字符(空格、tab键、换行符等),也就是说,scanf会从第一个非空白字符开始读取(Input white-space characters  are skipped, unless the specification includes a[,c,ornspecifier.)。比如,
  1. int a, b;
  2. scanf("%d%d", &a, &b);
复制代码
你输入"123 456"或"123         456"或"       123 456",scanf都可以分别给a和b赋值123和456。
%c是这条规则的例外:
  1. int c;
  2. scanf("%c", &c);
复制代码
如果你输入空格+回车,那么变量c的值就是空格,这可以通过prinf("%d", c)来验证,应该会输出32,也就是空格的ascii码编号。

2. 格式化字符串中如果包含了空白字符,则它匹配输入字符串中的0个或多个空白字符(A directive composed of white-space character(s) is executed by reading input up to the first non-white-space character)

好了,我想对于scanf,iOS开发的学习者了解以上内容就足够了。如有错讹,敬请大家批评指正!


作者: Medwyn    时间: 2014-7-3 11:22
不错的经验总结,没必要死记,用到的时候再查也不迟。
作者: 爱情路13号    时间: 2014-7-3 12:12
我自己也在总结这个东西,还有循环的结构,内循环,外循环,一不小心就错了
作者: 魇影    时间: 2014-7-3 12:49
总结的很详细嘛
作者: zhs    时间: 2014-7-3 13:09
总结的很不错,返回值这点学习了~
作者: 完美世界    时间: 2014-7-4 20:13
支持一下
作者: 我说你是个逗比    时间: 2014-9-13 17:09
总结的很专业,楼主强大




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2