scanf
函数
就如同
printf
函数用特定的格式显示输出一样,
scanf
函数也根据特定的格式读取输入。像
printf
函数的格式串一样,
scanf
函数的格式串也可以包含普通字符和转换说明两部分。
scanf
函数转换说明的用法和
printf
函数转换说明的用法本质上是一样的。
在许多情况下,
scanf
函数的格式串只包含转换说明,如下例所示:
int i, j;
float x, y;
scanf("%d%d%f%f", &i, &j, &x, &y);
假设用户录入了下列输入行:
1 -20 .3 -4.0e3
scanf
函数将读入上述行的信息,并且把这些符号转换成它们表示的数,然后分别把1、-20、0.3和-4000.0赋值给变量
i
、
j
、
x
和
y
。
scanf
函数调用中像
"%d%d%f%f"
这样“紧密压缩”的格式串是很普遍的,而
printf
函数的格式串很少有这样紧挨着的转换说明。
像
prinf
函数一样,
scanf
函数也有一些不易觉察的陷阱。使用
scanf
函数时,程序员必须检查转换说明的数量是否与输入变量的数量相匹配,并且检查每个转换是否适合相对应的变量。与
printf
函数一样,编译器无法检查出可能的匹配不当。另一个陷阱与符号
&
有关,符号
&
通常被放在
scanf
函数调用中每个变量的前面。符号
&
常常(但不总是)是需要的,记住使用它是程序员的责任。
如果
scanf
函数调用中忘记在变量前面放置符号
&
,将产生不可预知甚至可能是毁灭性的结果。程序崩溃是常见的结果。最轻微的后果则是从输入读进来的值无法存储到变量中,变量将保留原有的值(如果没有给变量赋初始值,那么这个原有值可能是没有意义的)。忽略符号
&
是极为常见的错误,一定要小心!一些编译器可以检查出这种错误,并产生一条类似“format argument is not a pointer”的警告消息。(术语指针将在第11章定义,符号
&
用于创建一个指向变量的指针。)如果抛出警告消息,检查一下是否遗漏了符号
&
。
调用
scanf
函数是读数据的一种有效但不理想的方法。许多专业的C程序员会避免使用
scanf
函数,而是采用字符格式读取所有数据,然后再把它们转换成数值形式。在本书中,特别是前面的几章将相当多地用到
scanf
函数,因为它提供了一种读入数的简单方法。但是要注意,如果用户录入了非预期的输入,那么许多程序都无法正常执行。正如稍后将看到的那样,可以用程序测试
scanf
函数(➤22.3节)是否成功读入了要求的数据(若不成功,还可以试图恢复)。但是,这样做对于本书的示例是不切实际的,因为这类测试将添加太多语句,从而掩盖示例的要点。
scanf
函数的工作方法
实际上
scanf
函数可以做的事情远远多于目前为止已经提到的这些。
scanf
函数本质上是一种“模式匹配”函数,试图把输入的字符组与转换说明相匹配。
像
printf
函数一样,
scanf
函数是由格式串控制的。调用时,
scanf
函数从左边开始处理字符串中的信息。对于格式串中的每一个转换说明,
scanf
函数从输入的数据中定位适当类型的项,并在必要时跳过空格。然后,
scanf
函数读入数据项,并且在遇到不可能属于此项的字符时停止。如果读入数据项成功,那么
scanf
函数会继续处理格式串的剩余部分;如果某一项不能成功读入,那么
scanf
函数将不再查看格式串的剩余部分(或者余下的输入数据),并立即返回。
在寻找数的起始位置时,
scanf
函数会忽略
空白字符
(white-space character,包括空格符、水平和垂直制表符、换页符和换行符)。因此,我们可以把数字放在同一行或者分为几行来输入。考虑下面的
scanf
函数调用:
scanf("%d%d%f%f", &i, &j, &x, &y);
假设用户录入3行输入:
1
-20 .3
-4.0e3
scanf
函数会把它们看作一个连续的字符流:
··1¤-20···.3¤···-4.0e3¤
(这里使用符号
·
表示空格符,符号
¤
表示换行符。)因为
scanf
函数在寻找每个数的起始位置时会跳过空白字符,所以它可以成功读取这些数。在接下来的示意代码中,字符下方的
s
表示此项被跳过,而字符下面的
r
表示此项被读取为输入项的一部分:
··1¤-20···.3¤···-4.0e3¤ ssrsrrrsssrrssssrrrrrr
scanf
函数“忽略”了最后的换行符,实际上没有读取它。这个换行符将是下一次
scanf
函数调用的第一个字符。
scanf
函数遵循什么规则来识别整数或浮点数呢?当要求读入整数时,
scanf
函数首先寻找正号或负号,然后读取数字,直到读到一个非数字时才停止。当要求读入浮点数时,
scanf
函数会寻找一个正号或负号(可选),随后是一串数字(可能含有小数点),再往后是一个指数(可选)。指数由字母
e
(或者字母
E
)、可选的符号,以及一个或多个数字构成。在用于
scanf
函数时,转换说明
%e
、
%f
和
%g
是可以互换的,这3种转换说明在识别浮点数方面都遵循相同的规则。
当
scanf
函数遇到一个不可能属于当前项的字符时,
它会把此字符“放回原处”,以便在扫描下一个输入项或者下一次调用
scanf
函数时再次读入。思考下面(公认有问题的)4个数的排列:
1-20.3-4.0e3¤
我们使用与以前一样的
scanf
函数调用:
scanf("%d%d%f%f", &i, &j, &x, &y);
下面列出了
scanf
函数处理这组新输入的方法。
%d
。第一个非空的输入字符是
1
;因为整数可以以
1
开始,所以
scanf
函数接着读取下一个字符,即
-
。
scanf
函数识别出字符
-
不能出现在整数内,因此把1存入变量
i
中,而把字符
-
放回原处。
%d
。随后,
scanf
函数读取字符
-
、
2
、
0
和
.
(句点)。因为整数不能包含小数点,所以
scanf
函数把–20存入变量
j
中,而把字符
.
放回原处。
%f
。接下来
scanf
函数读取字符
.
、
3
和
-
。因为浮点数不能在数字后边有负号,所以
scanf
函数把0.3存入变量
x
中,而把字符
-
放回原处。
%f
。最后,
scanf
函数读取字符
-
、
4
、
.
、
0
、
e
、
3
和
¤
(换行符)。因为浮点数不能包含换行符,所以
scanf
函数把
存入变量
y
中,而把换行符放回原处。
在这个例子中,
scanf
函数能够把格式串中的每个转换说明与一个输入项进行匹配。因为换行符没有读取,所以它将留给下一次
scanf
函数调用。
通过编写含有普通字符和转换说明的格式串能进一步地理解模式匹配的概念。处理格式串中的普通字符时,
scanf
函数采取的动作依赖于这个字符是否为空白字符。
scanf
函数从输入中重复读空白字符,直到遇到一个非空白字符(把该字符“放回原处”)为止。格式串中空白字符的数量无关紧要,格式串中的一个空白字符可以与输入中任意数量的空白字符相匹配。(附带提一下,在格式串中包含空白字符并不意味着输入中必须包含空白字符。格式串中的一个空白字符可以与输入中
任意
数量的空白字符相匹配,包括零个。)
scanf
函数将把它与下一个输入字符进行比较。如果两个字符相匹配,那么
scanf
函数会放弃输入字符,并继续处理格式串。如果两个字符不匹配,那么
scanf
函数会把不匹配的字符放回输入中,然后异常退出,而不进一步处理格式串或者从输入中读取字符。
例如,假设格式串是
"%d/%d"
。如果输入是
·5/·96
在寻找整数时,
scanf
函数会跳过第一个空格,把
%d
与
5
相匹配,把
/
与/相匹配,在寻找下一个整数时跳过一个空格,并且把
%d
与
96
相匹配。另一方面,如果输入是
·5·/·96
scanf
函数会跳过一个空格,把
%d
与
5
相匹配,然后试图把格式串中的
/
与输入中的空格相匹配。但是二者不匹配,因此
scanf
函数把空格放回原处,把字符
·/·96
留给下一次
scanf
函数调用来读取。为了允许第一个数后边有空格,应使用格式串
"%d /%d"
。
printf
函数和
scanf
函数
虽然
scanf
函数调用和
printf
函数调用看起来很相似,但这两个函数之间有很大的差异,忽略这些差异就是拿程序的正确性来冒险。
一个常见的错误是执行
printf
函数调用时在变量前面放置
&
。
printf("%d %d\n", &i, &j); /*** WRONG ***/
幸运的是,这种错误是很容易发现的:
printf
函数将显示一对样子奇怪的数,而不是变量
i
和
j
的值。
在寻找数据项时,
scanf
函数通常会跳过空白字符。因此除了转换说明,格式串通常不需要包含字符。另一个常见错误是假定
scanf
格式串应该类似于
printf
格式串,这种不正确的假定可能引发
scanf
函数行为异常。我们来看一下执行下面这个
scanf
函数调用时,到底发生了什么:
scanf("%d, %d", &i, &j);
scanf
函数首先寻找输入中的整数,把这个整数存入变量
i
中;然后,
scanf
函数将试图把逗号与下一个输入字符相匹配。如果下一个输入的字符是空格而不是逗号,那么
scanf
函数将终止操作,而不再读取变量
j
的值。
printf
格式串经常以
\n
结尾,但是在
scanf
格式串末尾放置换行符通常是一个坏主意。对
scanf
函数来说,格式串中的换行符等价于空格,两者都会导致
scanf
函数提前进入下一个非空白字符。例如,如果格式串是
"%d\n"
,那么
scanf
函数将跳过空白字符,读取一个整数,然后跳到下一个非空白字符处。像这样的格式串可能会导致交互式程序一直“挂起”,直到用户输入一个非空白字符为止。
程序 分数相加
为了显示scanf函数的模式匹配能力,考虑读入由用户输入的分数。分数通常的形式为 分子/分母 。scanf函数允许读入整个分数,而不用将分子和分母视为两个整数分别读入。下面的分数相加程序体现了这一方法。
addfrac.c
/* Adds two fractions */
#include <stdio.h>
int main(void)
{
int num1, denom1, num2, denom2, result_num, result_denom;
printf("Enter first fraction: ");
scanf("%d/%d", &num1, &denom1);
printf("Enter second fraction: ");
scanf("%d/%d", &num2, &denom2);
result_num = num1 * denom2 + num2 * denom1;
result_denom = denom1 * denom2;
printf(`"`The sum is %d/%d\n`"`, result_num, result_denom);
return 0;
}
运行这个程序,可能的显示如下:
Enter first fraction: 5/6 Enter second fraction: 3/4 The sum is 38/24
注意,结果并没有化为最简分数。