在做PTA时,经常会碰到多行输入或混合数据类型的情况。此时若对scanf没有很好的理解,那么就会不可避免地产生一些隐秘的bug,从而致使“部分正确”甚至“答案错误”,消磨心神。
这篇文章主要从输入与scanf读取两个角度,对输入数据的分类、格式化输入、终端输入与缓冲区以及scanf的原理做了详细分析与理解,供大家参考。
注:以下由外界输入的形式一律按照“...-...-...”
一、输入
1、输入数据的分类
1.1 数
数主要分为整型和浮点型,包括数字
(这篇文章里认为数字即0~9)
1.2 字符
字符分为空白字符与非空白字符。空白字符包括空格、制表、回车
(严格来讲在windows系统下实际是换行,但这篇文章方便起见均以回车表述)
1.3 数字
即0~9的十个数字,既可以是数,又可以是字符
(具体怎么区分,依据格式化输入的类型判定。有时候忽略了这一点会导致%c读取数字)
例如下面这个例子
char c;
scanf("%c",&c);
输入“123”,则a会被赋值为字符1
2、格式化输入
2.1 用户按指定格式从终端上用键盘输入数据到指定的变量中
此时就可以分为合法情况与非法情况
合法情况:用户输入的数据满足程序需求的格式,输入与需求对应全都匹配
非法情况:用户输入的数据不满足程序需求的格式,输入与需求存在不匹配
int n1,n2;
char c1,c2;
scanf("%d%c%d%c",&n1,&c1,&n2,&c2);
输入“1-a-2-b-回车”,则为合法;输入“1-2-a-b-回车”,则为非法
2.2 当数据满足格式化输入的形式时,以回车作为输入的结束
此处的“格式化输入的形式”指的是程序需求输入多少的数据,更接近“个数”的概念。需要与合法情况区分开来
切记,结束输入的这个回车会被留在缓冲区内,既作为这次输入的最后一个字符,同时也是下次输入的第一个字符
例如上述的“1-a-2-b-回车”,这个回车会被留在缓冲区。
3、终端输入与缓冲区
3.1 缓冲区数据不足时,终端请求输入
终端输入的数据会被缓存在缓冲区中,直到终端结束输入后再由程序读取
缓冲区存在的原因是便于输入时数据的增删修改
3.2 缓冲区数据足够时,终端结束输入
缓冲区所剩的数据若满足格式化输入的形式,那么终端不会再请求输入。这种情况并非是终端结束输入,而是终端不请求输入。有时多行输入数据会遇到终端不请求输入的情况。
int n;
char c;
scanf("%d%c",&n,&c);
scanf("%d%c",&n,&c);
输入“1-a-2-b-3-c-回车”,第一个scanf因为缓冲区没有数据,所以终端会请求输入;第二个scanf因为缓冲区的数据足够,所以终端不会请求输入。
二、scanf读取
1、合法情况下
scanf对不同的数据类型有不同的特性
对于字符型%c:scanf会读取第一个空白字符
实质就是无论第一个是什么字符scanf均会读取
注意,这里的“第一个”是相对于scanf中%c的位置而言
对于整型%d或浮点型%f:scanf会读取第一个非空白字符
而在第一个非空白字符前的所有空白字符均不会被读取
注意,这里的“第一个”是相对于scanf中%d或%f的位置而言
int n;
char c;
scanf("%d%c",%n,&c);
输入“空格(无论多少个)/回车(无论多少个)/制表(无论多少个)-123-空格/制表/非空白字符-回车”
则n被赋值为123,c被赋值为空格/制表/非空白字符
输入“空格(无论多少个)/回车(无论多少个)/制表(无论多少个)-123-回车”
则n会被赋值为123,c会被赋值为\n。这一点,尤其难发现。
int n;
char c;
scanf("%c%d",%c,&n); //注意上次的代码与这次的有差异
输入“空格/回车/制表/非空白字符-空格(无论多少个)/回车(无论多少个)/制表(无论多少个)-123-回车”
则c被赋值为空格/回车/制表/非空白字符,n被赋值为123
2、非法情况下
scanf会从第一个类型不匹配的数据开始停止此次的读取
直到输入数据满足格式化输入的形式后执行下一行代码
此时,从第一个类型不匹配的数据开始(包括该数据),直到输入的数据足够后,这期间输入的数据会被缓存在缓冲区。之前的数据则被清空,或者说不予理会。
int n1,n2;
char c1,c2;
scanf("%d%c%d%c",&n1,&c1,&n2,&c2);
输入“1-a-b-2-回车”,则n1被赋值为1,c1被赋值为a,n2与c2不被赋值
此时,“b-2-回车”被缓存到缓冲区,等待下一次的读取
下一个scanf则会从缓冲区的第一个数据开始读取
此时,你仍有可能会在终端上被要求输入数据(这取决于缓冲区中的数据是否足够)。可是要明白此时真正被scanf正在读取的并非你输入的数据,而是缓冲区中的数据。但是你输入的数据仍然要满足格式化输入的形式才能结束输入,此时你输入的数据与缓冲区已有的数据一并被scanf读取,若合法则结束读取;若不合法则继续上述非法情况
int n1,n2;
char c1,c2;
scanf("%d%c%d%c",&n1,&c1,&n2,&c2);
scanf("%c%d%d%c",&c1,&n1,&n2,&c2); //注意上一行与此行代码有差异
情况一,缓冲区数据不足:
输入“1-a-b-2-回车”,则n1被赋值为1,c1被赋值为a,n2与c2不被赋值
此时,“b-2-回车”被缓存到缓冲区,等待下一次的读取
执行到下一行代码,scanf需要读取,然而此时缓冲区的数据不满足格式化输入的形式,因此终端请求输入
输入“3-c-4-d-回车”,但真正被scanf读取的数据是“b-2-回车-3-c-4-d-回车”
则c1被赋值为b,n1被赋值为2,n2被赋值为3,c2被赋值为c
情况二,缓冲区数据足够:
输入“a-1-2-b-回车”,则n1、c1、n2、c2均不被赋值
此时,“a-1-2-b-回车”被缓存在到缓冲区,等待下一次的读取
执行到下一行代码,scanf需读取,然而此时缓冲区的数据满足格式化输入的形式,因此终端不请求输入
则c1被赋值为a,n1被赋值为12,n2与c2不被赋值,缓冲区剩下“b-回车”
总结:
只要对这篇文章所提到的每一点都有深刻真切的领会,并能牢记,在分析自己写的代码时运用自如,那么对于scanf就不会有更多的疑惑。共勉。