14.2.2 标准输入函数scanf

    scanf函数用于从标准输入设备(通常是键盘)读取流数据到内存中,同printf函数的使用方法几乎一致,其参数表也分为两部分,即控制字符串和参数表,基本格式如下所示。


    int scanf(const char*,&para1,&para2……);

    scanf函数中用于保存读入值的变元必须都是变量指针,即相应变量的地址。scanf函数的参数表长度也是非固定的,而且转换说明符也相对简单,同printf函数中的转换说明符完全一样。因此scanf函数的使用也十分灵活,如以下简单的示例代码14.2所示。

    代码14.2 标准输入函数scanf的基本用法ScanfSample


    <————————————文件名:example1402.cpp———————————————> 01 #include<cstdio> 02 int main() 03 { 04 char a,s[20];//声明一个char型变量a,一个字符数组s 05 double b;//double型变量b 06 int i;//int型变量i 07 scanf("%c%s%d%Lf",&a,s,&i,&b);//依次从标准输入流中读取字节信息放入a、 08 //s、i和b对应的内存空间中 09 printf("a is%c,s is%s,i is%d,b is%Lf",a,s,i,b);//输出处理 10 return 0; 11 }

    输出结果如下所示。


    w(用户输入w后回车) hello(用户输入hello后回车) 20 (用户输入20后回车) 3.1415(用户输入3.1415后回车) a is w,s is hello,i is 20,b is 3.141500

    【代码解析】代码第7行的scanf函数按照控制字符串中的转换说明把标准输入流中的字符转换成值存储到参数表达中相应指针指向的内存区域,这个过程常称“扫描”。先对扫描进行形象的说明,输入扫描的过程是一个从左至右逐个匹配的过程,“回车”起到将缓冲区中的数据交付给scanf函数处理和分割输入项的作用,以便更好地完成输入与变量的匹配,关于匹配的内容稍后会有介绍。

    注意

    代码14.2 中字符数组名s本身即是指针,不能再使用&s的形式。

    控制字符串有以下3部分组成。

    ❑格式说明符

    ❑空白符

    ❑非空白符

    1.格式说明符

    首先来看一下格式说明符,scanf函数的格式说明符与printf函数大致相同,同样是由%和转换字符组成,scanf的转换字符如表14.2所示。

    14.2.2 标准输入函数scanf - 图1

    此外同printf函数一样,scanf函数也支持一些可选的标记,如可以将h放在转换字符d、i、o、u和x前面,表示被转换的值以short int和unsigned short int型存储,可以将l放在转换字符d、i、o、u、x和X的前面,表示被转换的值以long int或unsignedt long int型存储,如果将l放在转换字符e、E、f、g或G前,表示被转换的值以double型参数存储,如果将l放在n前,那么相应的参数是指向long int或unsigned long int的指针。如果将L放在转换字符e、E、f、g或G前,表示转换后的值以long double型存储。

    %与转换字符间的星号(*)表示读指定类型的数据但不保存。因此有如下代码所示。


    scanf("%d%*c%d",&x,&y);

    输入“10/20”,10存入变量x,20存入变量y,字符'/'虽被读取,但并不保存。

    此外,还可指定扫描宽度,即扫描的字符数,默认是输入流的长度,例如,%s跳过空白字符,然后读入非空白字符,直到遇见空白字符或文件结束标记,与此对照,%8s跳过前导空白符,读入非空白字符,直到读入了5个字符、遇到空白字符或文件结束标记位置。可以用%nc读入n个字符,其中包括空白字符。

    2.空白符

    在控制字符串中但不在转换说明中的空白字符称为空白符,其可以和输入流中的空白字符匹配,也可以不匹配,换言之,控制字符串中的一个空白符可以和标准输入流中的任意个连续空白(包括0个)匹配。

    3.普通字符

    控制字符串中除了空白符和转换说明符以外的字符称为普通字符,普通字符必须与输入流中的字符相匹配,否则当前扫描结束,scanf函数退出,如下例所示。


    int num; scanf("H%d",&num);

    通过键盘输入时,必须先输入字符H以完成匹配,才能进一步输入一个整数值,存储到num中,如果一开始输入的不是字符H,匹配失败,scanf函数退出,并不会将数据存储到num中,反而会在当前缓冲区中留下垃圾。

    除了%c外,诸如%d和%f等读数操作和%s字符串读入都会自动跳过前导空格,如下所示。


    scanf("%d",&num);//输入"□□□□2",则num=2,前面的空格被自动跳过 scanf("%s",str);//输入"□hello China",则str为"Hello",遇到空格停止 scanf("%c",&chr);//输入"□x",则chr内容为空白,不会自动跳过前导空白

    要想使%c同样可以跳过前导空白,可以使用空白符的形式,如下所示。


    scanf("%c",&chr);//输入"□□□□x",chr中的内容为'x'调用scanf函数时,可能会因为不匹配发生输入失败,如果输入流中没有字符,返回值为EOF(根据不同的系统取不同的值,一般是-1),若发生匹配失败,非法的字符被留在输入流(缓冲区)中,返回已成功转换和存储的字符数,如果没有进行任何成功的转换,返回值就是0,如果全体匹配成功,即scanf成功,返回成功转换的字符数,如下所示。 scanf("%d",&num);

    如果输入“w”,无法将其转换为十进制整数,匹配失败,返回已成功转换的字符数0,但需要注意的是此时w仍滞留在输出流(缓冲区)中,如果不加妥善处理,会给后续程序带来问题,这称为“缓冲区垃圾”,如示例代码14.3所示。

    代码14.3 缓冲区垃圾BufferSample


    <———————————文件名:example1403.cpp————————————————> 01 #include<cstdio> 02 int main() 03 { 04 int num; 05 char chr; 06 scanf("%d",&num);//读取十进制整数到num中 07 fflush(stdin);//清空输入缓冲区 08 scanf("%c",&chr);//读取一个字符到chr中 09 printf("chr is%c\n",chr); 10 return 0; 11 }

    输出结果如下所示。


    w(用户输入) chr is w

    【代码解析】代码第6行的本意是先输入int型变量num的值,再输入char型变量chr,可输入num值时出现了错误,系统无法将字符w解释为int型数据,匹配失败,但已经输入的字符w并没有从缓冲区中清除出去,这样代码第8行检查到缓冲区中有字符w,便自动完成了对chr的赋值,而不是由用户完成输入的,这就是常见的缓冲区垃圾现象,稍不留意便会给程序带来意想不到的后果。

    常规的解决思路是清除输入缓冲区函数“fflush(stdin)”,将这行语句添加到代码第7行,问题得到了解决,该函数能将缓冲区中当前内容清空,另一个方法是不断读出数据直到缓冲区变空,while((c=getchar())!='\n'&&c!=EOF),getchar函数的原形如下所示。


    int getchar(void);

    用于从当前标准输入流中读取并返回一个字符。

    注意

    在输入流中,数据项必须由空格、制表符和换行符分割。逗号和分号等不是分隔符,如以下代码所示。


    scanf("%d%d",&r,&c);

    程序接受输入“10 20”或“10回车20”,但遇到“10,20”则运行失败。