在文章里,我们详细介绍了标准输入流stdin及其库函数getchar、gets、gets_s,以及针对gets和gets_s函数的不足,建议使用fgets函数的原因和用法。
gets函数、gets_s函数、fgets函数都属于无格式化的输入函数,也就是说输入什么就读取什么,而且只能读取char类型字符串,无法读取其他类型数据,无法对输入的字符进行控制。
从本篇开始我们介绍stdin的另一种函数:格式化输入可函数。这些库函数可以对输入的字符进行各种控制(解析),比如可以输入不同类型的数据,也可以控制输入数据的宽度,对应的库函数是scanf和它的安全版本scanf_s,以及scanf函数的可变参版本vscanf以及对应的安全版本vscanf_s。
我们先从scanf函数详细讲起。
scanf函数这个函数是C语言格式化输入函数的最重要的一个函数,今天我们详细的学习一下。
函数原型:
intscanf(constchar*format,...);
参数format是一个字符串,这个字符串里的不同字符按照特定的规则结合在一起,可以实现各种“格式控制功能”,用来对键盘上输入的字符按照这些“格式控制指令”的要求进行解析后再读取到内存中。
听起来好像比较复杂,其实很简单。比如:
scanf(“%d”,d);
字符串”%d”中的%d就是一个格式控制指令,其中%表示后面紧跟的某些特定字母(不是字符)是类型说明符,比如此处%后面紧跟的字母是d,就表示十进制整数(decimal),%和d共同构成一个变量占位符“%d”,表示后面的参数对应的变量必须是整型变量,该参数就是指向变量d的指针,此处就是参数d。
注意,%d中的d就是类型说明符,表示只能接收键盘上输入十进制整数,不接受其他进制整数的输入。d中的d是整型变量名。
d的d是一个整型变量,d表示传入scanf函数的是变量d的地址。格式字符串里有几个变量占位符,就对应后面有几个对应类型的变量地址。
下面是一个简单的例子:
intd;
charc;
floatf;
scanf("%d%c%f",d,c,f);
printf("%d,%c,%f\n",d,c,f);
完整代码截图如下:
图1程序的运行效果如下图:
图2上面的演示程序里,scanf的格式字符串里有个变量占位符,分别是int型、char型号、float型变量,变量占位符和后面的参数类型是按照先后顺序一一对应的。
键盘输入的数据如何分隔?变量站位符中间有空格作为分隔符,键盘输入对应数据时就必须要同样输入对应的分隔符。比如中间如果用1个逗号分隔:
scanf("%d,%c,%f",d,c,f);
输入的时候就必须也只能输入1个逗号,不能多输入。其他分隔符的输入数量也必须严格遵守,不过空格分隔符是个例外,无论在格式字符串里输入多少个空格,在键盘上输入空格时,数量不需要一致。
也可以用换行符作为分隔符。比如:
scanf("%d\n%c\n%f",d,c,f);
注意!换行符,以及制表符(TAB键或’\t’)都被作为空白字符对待!scanf函数在扫描时如果没有空白符,也不会解析错误,会忽略,但如果换成其他分隔符,比如逗号等,就容易产生解析错误。
scanf和printf格式字符串的相似和不同当我们输入小数(实数)时,我们明明输入了2位小数,但是系统却没有原样输出,而是给出了6位,那是因为float型的十进制小数位数默认就是6位。
如果希望能原样输出,就可以在printf函数中的%f中间插入%0.2f,或者%后面直接是小数点,省略数字0,即%.2f,表示输出的小数无论存储的是小数点后多少位,输出的时候只显示小数点后2位。
键盘输入了大于2位小数,比如.,输出的时候也只能是.14。
但是,scanf函数中的float型变量占位符中间不能插入小数点后位数的宽度限制,只能在printf函数使用!
通过刚才的这个例子,我们会发现,printf函数和scanf的用法很相似,特别是在格式字符串方面,但是还是有一些细微差别的,比如可变参数列表里的变量就不需要参数的实际地址,只需要参数名即可。后面我们会有文章来详细讲解和对照分析。
如何实现数据类型的不同输入形式?
整数的不同输入形式如果我们需要输入一个整型数据,对应的类型说明符d(十进制:decimal),表示键盘上只能以十进制的形式输入。如果输入一个十六进制数据,scanf函数通过%d就无法识别。
类型说明符o(八进制:octal)表示接收键盘输入的八进制整数,类型说明符x(十六进制:hexadecimal)表示接收键盘输入十六进制数。类型说明符u(无符号的:unsigned)表示从键盘上接收无符号的整数(正整数)。
scanf函数还提供了%i,这样我们在键盘上可以输入八进制、十进制、十六进制都可以。如果输入八进制,必须要以数字0开头,要输入十六进制,要以0x(数字0和x)开头。
我们通过一个例子来熟悉下:
scanf("%d",decimal);
scanf("%o",octal);
scanf("%x",hex);
scanf("%u",unsigned_num);
scanf("%i",i);
在vs环境下的完整代码截图如下
图程序运行结果如下:
图4当%o和%x时,直接输入对应进制的数值即可,当为%i时,输入8进制和16进制时,需要注意输入数字的前缀。如果前缀是0,表示后面必须输入八进制,但如果错误输入,比如输入8,即08,程序就异常结束。如果前缀是0x,后面输入的就是16进制的数字。
单精度和双精度浮点数的类型说明符C语言中浮点数分为单精度浮点数和双精度浮点数,单精度浮点数小数点后最多为6位,用float表示,双精度浮点数小数点后为15位,用double表示。
float型变量的类型说明符为f,%f表示键盘上最多只能输入6位小数。double型变量采用%lf表示,lf是longfloat的缩写,键盘上可以输入最多15位小数。
浮点数的科学计数法输入形式浮点数也可以以科学计数法的形式输入。比如41.26,用科学计数法表示就是.*10^2,在键盘输入时,10用字母e或E表示,写成.e2即可。
对应的scanf的类型说明符为e(指数exponential),比如:
floate;
scanf(“%e”,e);
程序运行后,键盘上可以输入.e2即可。注意,如果输入的浮点数是double类型,可以用%le表示。
相似数据类型的大小前缀修饰符double型修饰符:%lf、%le
在上面我们多次提到单精度浮点数的类型说明符是f,如果需要输入double类型,就用字母l修饰,%lf中l就是大小修饰符,%e也可以用l修饰,%le表示double类型数据的科学计数法形式。
longdouble型修饰符:%Lf、%Le
如果是longdouble型数据,就需要大写字母L修饰:%Lf、%Le。
long型修饰符(长整型:longint)
如果需要输入长整型变量,那么所有整型变量的修饰符前都可以直接用字母l修饰。比如:%ld、%lo、%lx、%lu、%li。
longlong型修饰符
用两个字母l修饰即可,如%lld、%llo、%llx、%lli。
short型修饰符号(短整型:shortint)
用字母h修饰,如%hd、%ho、%hx、%hu、%hi。
字符串的输入和宽度限制scanf函数可以接收字符串的输入,对应的类型说明符是字符串string的s,因为C语言没有“字符串”(string)这种基本数据类型,所以一般都是用字符数组(或malloc分配的堆内存来接收,不懂也没关系)来存储。
一般不能直接用%s,比如面的写法是错误的:
charstr[5];
scanf(“%s”,str);
如果持续看我文章的读者,应该知道怎么回事。因为我在之前的文章里多次说到,字符串的输入一定要明确的规定其输入的字符个数(长度),不然很容易会因为输入的字符个数超过接收的数组的长度,而造成缓冲区溢出的危险。
既然字符数组str的长度是5,所以我们就需要通过格式控制指令来告诉scanf函数,键盘上接收的当前字符串长度只能不能大于5个字符,否则就会造成数组越界。
但是由于明确知道是字符串,所以scanf函数会自动在接受键盘上输入的字符串(严格的说,此时还不能叫字符串,应该是多个字符)之后填上’\0’空字符,这时候才是真正合格的字符串,然后复制到str指向的区域。
因此,考虑到scanf函数要在字符末尾添加空字符,所以实际上键盘上最大只能接收4个字符。当然,也可以接受1到个字符。
所以,在%s中间插入要限制的字符个数,只能是1到4。比如是%1s表示键盘输入的字符串的个数只能是1个字符,实际上存入str区域的是2个字节。str[0]是键盘上接收的有效字符,str[1]’\0’。
以此类推,%2s、%s、%4s分别表示str[2]、str[]、str[4]存储的都是空字符。
我们一般把%s中间的数字称为宽度限制字段。
宽度修饰符宽度修饰符也可以用来修饰字符、整数、浮点数的整数部分等。如下:
chars1[],s2[],s[];
scanf(“%2s%s%c”,s1,s2,s);
%2s表示只从键盘接收2个有效字符并且自动加上第三个空字符,构成一个三字节的字符串。假设键盘上输入字母a和b,那么:
s1[0]=‘a’;
s1[1]=‘b’;
s2[2]=‘\0’;
%s会让数组s2下标越界,是错误的,会造成程序可能异常退出。
%c表示从键盘上连续接收个有效字符,如果键盘上输入’w’、’t’、’o’,那么:
s2[0]=‘w’;
s2[1]=‘t’;
s22[2]=‘o’;
不会像字符串一样在末尾自动添加空字符’\0’。一般情况下,如果我们需要获得一个字符串,就会用类型说明s,如果我们需要获得多个字符,就会用类型说明符c和宽度限制符结合起来使用。
宽度修饰符也经常被用在对整数的宽度限制上。比如:
intn1,n2;
scanf(“%d%1d”,n1,n2);
如果键盘上有如下三组输入:
2
我们来分析下,首先第一行输入连续的一组数字是12,正好符合第一个变量的格式要求:1到位整数,因此n1=12。然后空格进行分割,紧接着的数字1也正好满足第二个变量的格式要求:1位整数,因此n2=1。
第二行的第一组数字是12,符合变量n1的宽度要求,%d表示1到位整数。第二组数字12会从左往右只截取数字1赋值给n2,因为n2的最大宽度是1。
第三行的第一组数字是,n1的最大宽度为,因此n1=12没问题,因为n2和n1本来需要有空格分隔,但没有输入空格,直接是数字4,注意!!!没有空格时,scanf函数直接忽略,将数字4赋值给n2,然后停止解析。
注意!如果用其他符号,比如逗号作为分隔符,scanf函数就不会忽略,解析就会出错。比如:
intn1,n2;
scanf(“%d,%1d”,n1,n2);
输入一串数字:
,1
n1仍然是12,但是紧随其后的不是逗号,是数字4,因此scanf函数的解析是错误的,只有空格没有时可以被忽略,其他的分隔符在输入时必须保持一致,因此终止解析。
%c和%1c、%d和%1d的宽度区别%c和%1c是相同的含义,表示读取1个字符。%d和%1d(是数字1,不是字母l)是完全不同的含义。%1d表示最多只能读取1位数字,%d表示读取的数字位数没有限制。
只匹配、不存储的通配符:*百分号后面的星号(*)将取消下一个输入字段的分配(将被解释为指定类型的字段)。将扫描该字段但不将其存储在参数中。
举个例子:
intn1,n2;
scanf(“%d%*2d%d”,n1,n2);
因为格式字符串有个变量占位符,因此键盘上必须输入个整型变量,并且第二个变量占位符明确指定了该整型变量的宽度是2,因此下面是一个合理的输入:
第一个整数12和第三个整数将被存储到n和n2里,4将作为校验码只被scanf函数检查输入是否匹配格式指令,而不被存储到任何变量。
总结今天介绍了scanf函数的格式字符串中很多重要的格式控制码的用法,这些都是非常重要、基础的用法。下一章我们将介绍scanf函数的格式控制字符串中的最后一个用法:正则表达式。
段誉,年6月20日,写于合肥。