15-使用scanf()
4.4.5 使用 scanf()
刚学完输出,接下来我们转至输入——学习 scanf()
函数。C库包含了多个输入函数, scanf()
是最通用的一个,因为它可以读取不同格式的数据。当然,从键盘输入的都是文本,因为键盘只能生成文本字符:字母、数字和标点符号。如果要输入整数 2014
,就要键入字符 2
、 0
、 1
、 4
。如果要将其存储为数值而不是字符串,程序就必须把字符依次转换成数值,这就是 scanf()
要做的。 scanf()
把输入的字符串转换成整数、浮点数、字符或字符串,而 printf()
正好与它相反,把整数、浮点数、字符和字符串转换成显示在屏幕上的文本。
scanf()
和 printf()
类似,也使用格式字符串和参数列表。 scanf()
中的格式字符串表明字符输入流的目标数据类型。两个函数主要的区别在参数列表中。 printf()
函数使用变量、常量和表达式,而 scanf()
函数使用指向变量的指针。这里,读者不必了解如何使用指针,只需记住以下两条简单的规则:
- 如果用
scanf()
读取基本变量类型的值,在变量名前加上一个&
; - 如果用
scanf()
把字符串读入字符数组中,不要使用&
。
程序清单4.15中的小程序演示了这两条规则。
程序清单4.15 input.c
程序
// input.c -- 何时使用&
#include <stdio.h>
int main(void)
{
int age; // 变量
float assets; // 变量
char pet[30]; // 字符数组,用于存储字符串
printf("Enter your age, assets, and favorite pet.\n");
scanf("%d %f", &age, &assets); // 这里要使用&
scanf("%s", pet); // 字符数组不使用&
printf("%d $%.2f %s\n", age, assets, pet);
return 0;
}
下面是该程序与用户交互的示例:
Enter your age, assets, and favorite pet.
38
92360.88 llama
38 $92360.88 llama
scanf()
函数使用空白(换行符、制表符和空格)把输入分成多个字段。在依次把转换说明和字段匹配时跳过空白。注意,上面示例的输入项(粗体部分是用户的输入)分成了两行。只要在每个输入项之间输入至少一个换行符、空格或制表符即可,可以在一行或多行输入:
Enter your age, assets, and favorite pet.
42
2121.45
guppy
42 $2121.45 guppy
唯一例外的是 %c
转换说明。根据 %c
, scanf()
会读取每个字符,包括空白。我们稍后详述这部分。
scanf()
函数所用的转换说明与 printf()
函数几乎相同。主要的区别是,对于 float
类型和 double
类型, printf()
都使用 %f
、 %e
、 %E
、 %g
和 %G
转换说明。而 scanf()
只把它们用于 float
类型,对于 double
类型要使用 l
修饰符。表 4.6
列出了 C99
标准中常用的转换说明。
| 转换说明 | 含义 |
| :----- | :----- | :----- | :----- |
| %c
| 把输入解释成字符 |
| %d
| 把输入解释成有符号十进制整数 |
| %e
、 %f
、 %g
、 %a
| 把输入解释成浮点数(C99标准新增了 %a
) |
| %E
、 %F
、 %G
、 %A
| 把输入解释成浮点数(C99标准新增了 %A
) |
| %i
| 把输入解释成有符号十进制整数 |
| %o
| 把输入解释成有符号八进制整数 |
| %p
| 把输入解释成指针(地址) |
| %s
| 把输入解释成字符串。从第1个非空白字符开始,到下一个空白字符之前的所有字符都是输入 |
| %u
| 把输入解释成无符号十进制整数 |
| %x
、 %X
| 把输入解释成有符号十六进制整数 |
可以在表4.6所列的转换说明中(百分号和转换字符之间)使用修饰符。如果要使用多个修饰符,必须按表4.7所列的顺序书写。
| 转换说明 | 含义 |
| :----- | :----- | :----- | :----- |
| | 抑制赋值(详见后面解释) | 示例: "%
d"
|
| 数字 | 最大字段宽度。输入达到最大字段宽度处,或第 1
次遇到空白字符时停止 | 示例: "%10s"
|
| hh
| 把整数作为 signed char
或 unsigned char
类型读取 | 示例: "%hhd"
、 "%hhu"
|
| ll
| 把整数作为 long long
或 unsigned long long
类型读取( C99
) | 示例: "%lld"
、 "%llu"
|
| h
、 l
或 L
| "%hd"
和 "%hi"
表明把对应的值存储为 short int
类型 | "%ho"
、 "%hx"
和 "%hu"
表明把对应的值存储为 unsigned short int
类型 | "%ld"
和 "%li"
表明把对应的值存储为 long
类型 | "%lo"
、 "%lx"
和 "%lu"
表明把对应的值存储为 unsigned long
类型 | "%le"
、 "%lf"
和 "%lg"
表明把对应的值存储为 double
类型 | 在 e
、 f
和 g
前面使用 L
而不是 l
,表明把对应的值被存储为 long double
类型。如果没有修饰符, d
、 i
、 o
和 x
表明对应的值被存储为 int
类型, f
和 g
表明把对应的值存储为 float
类型 |
| j
| 在整型转换说明后面时,表明使用 intmax_t
或 uintmax_t
类型(C99) | 示例: "%jd"
、 "%ju"
|
| z
| 在整型转换说明后面时,表明使用 sizeof
的返回类型(C99) | 示例: "%zd"
、 "%zo"
|
| t
| 在整型转换说明后面时,表明使用表示两个指针差值的类型(C99) | 示例: "%td"
、 "%tx"
|
如你所见,使用转换说明比较复杂,而且这些表中还省略了一些特性。省略的主要特性是,从高度格式化源中读取选定数据,如穿孔卡或其他数据记录。因为在本书中, scanf()
主要作为与程序交互的便利工具,所以我们不在书中讨论更复杂的特性。
1.从 scanf()
角度看输入
接下来,我们更详细地研究 scanf()
怎样读取输入。假设 scanf()
根据一个 %d
转换说明读取一个整数。 scanf()
函数每次读取一个字符,跳过所有的空白字符,直至遇到第 1
个非空白字符才开始读取。因为要读取整数,所以 scanf()
希望发现一个数字字符或者一个符号( +
或 -
)。如果找到一个数字或符号,它便保存该字符,并读取下一个字符。如果下一个字符是数字,它便保存该数字并读取下一个字符。 scanf()
不断地读取和保存字符,直至遇到非数字字符。如果遇到一个非数字字符,它便认为读到了整数的末尾。然后, scanf()
把非数字字符放回输入。这意味着程序在下一次读取输入时,首先读到的是上一次读取丢弃的非数字字符。最后, scanf()
计算已读取数字(可能还有符号)相应的数值,并将计算后的值放入指定的变量中。
如果使用字段宽度, scanf()
会在字段结尾或第 1
个空白字符处停止读取(满足两个条件之一便停止)。
如果第 1
个非空白字符是 A
而不是数字,会发生什么情况? scanf()
将停在那里,并把 A
放回输入中,不会把值赋给指定变量。程序在下一次读取输入时,首先读到的字符是 A
。如果程序只使用 %d
转换说明, scanf()
就一直无法越过 A
读下一个字符。另外,如果使用带多个转换说明的 scanf()
,C规定在第 1
个出错处停止读取输入。
用其他数值匹配的转换说明读取输入和用 %d
的情况相同。区别在于 scanf()
会把更多字符识别成数字的一部分。例如, %x
转换说明要求 scanf()
识别十六进制数 a
~ f
和 A
~ F
。浮点转换说明要求 scanf()
识别小数点、 e
记数法(指数记数法)和新增的 p
记数法(十六进制指数记数法)。
如果使用 %s
转换说明, scanf()
会读取除空白以外的所有字符。 scanf()
跳过空白开始读取第 1
个非空白字符,并保存非空白字符直到再次遇到空白。这意味着 scanf()
根据 %s
转换说明读取一个单词,即不包含空白字符的字符串。如果使用字段宽度, scanf()
在字段末尾或第 1
个空白字符处停止读取。无法利用字段宽度让只有一个 %s
的 scanf()
读取多个单词。最后要注意一点:当 scanf()
把字符串放进指定数组中时,它会在字符序列的末尾加上 '\0'
,让数组中的内容成为一个C字符串。
实际上,在C语言中 scanf()
并不是最常用的输入函数。这里重点介绍它是因为它能读取不同类型的数据。C语言还有其他的输入函数,如 getchar()
和 fgets()
。这两个函数更适合处理一些特殊情况,如读取单个字符或包含空格的字符串。我们将在第 7
章、第 11
章、第 13
章中讨论这些函数。目前,无论程序中需要读取整数、小数、字符还是字符串,都可以使用 scanf()
函数。
2.格式字符串中的普通字符
scanf()
函数允许把普通字符放在格式字符串中。除空格字符外的普通字符必须与输入字符串严格匹配。例如,假设在两个转换说明中添加一个逗号:
scanf("%d,%d", &n, &m);
scanf()
函数将其解释成:用户将输入一个数字、一个逗号,然后再输入一个数字。也就是说,用户必须像下面这样进行输入两个整数:
88,121
由于格式字符串中, %d
后面紧跟逗号,所以必须在输入 88
后再输入一个逗号。但是,由于 scanf()
会跳过整数前面的空白,所以下面两种输入方式都可以:
88, 121
和
88,
121
格式字符串中的空白意味着跳过下一个输入项前面的所有空白。例如,对于下面的语句:
scanf("%d ,%d", &n, &m);
以下的输入格式都没问题:
88,121
88 ,121
88 , 121
请注意,“所有空白”的概念包括没有空格的特殊情况。
除了 %c
,其他转换说明都会自动跳过待输入值前面所有的空白。因此, scanf("%d%d", &n, &m)
与 scanf("%d %d", &n, &m)
的行为相同。对于 %c
,在格式字符串中添加一个空格字符会有所不同。例如,如果在格式字符串中把空格放到 %c
的前面, scanf()
便会跳过空格,从第 1
个非空白字符开始读取。也就是说, scanf("%c", &ch)
从输入中的第 1
个字符开始读取,而 scanf(" %c", &ch)
则从第 1
个非空白字符开始读取。
3. scanf()
的返回值
scanf()
函数返回成功读取的项数。如果没有读取任何项,且需要读取一个数字而用户却输入一个非数值字符串, scanf()
便返回 0
。当 scanf()
检测到“文件结尾”时,会返回 EOF
( EOF
是 stdio.h
中定义的特殊值,通常用 #define
指令把 EOF
定义为 -1
)。我们将在第 6
章中讨论文件结尾的相关内容以及如何利用 scanf()
的返回值。在读者学会 if
语句和 while
语句后,便可使用 scanf()
的返回值来检测和处理不匹配的输入。