The C Programming Language(第 2 版) 笔记 / 7 输入与输出 / 7.4 格式化输入(scanf 函数)

目录、参考文献


7.4 格式化输入(scanf 函数)

输入函数 scanf 对应于输出函数 printf,它在与后者相反的方向上提供同样的转换功能
具有变长参数表的函数 scanf 的声明形式如下:

int scanf(char *format, ...)

scanf 函数从标准输入中读取字符序列,按照 format 中的格式说明对字符序列进行解释,并把结果保存到其余的参数中
格式参数 format 定义解析的规则,其它所有参数都必须是指针,用于指定经格式转换后的相应输入保存的位置

在命令行中运行时,C 程序在调用 scanf 函数时,如果标准输入中没有未读的值,则程序会阻塞,控制传递给命令行程序
命令行中输入非换行符的字符时,C 程序会继续阻塞
直到输入换行后,命令行程序才会把控制传递给 scanf 函数,将读入的一行字符序列放到 C 程序的标准输入
scanf 会将标准输入中的字节数组当做字符序列进行解析
如果标准输入中的字节数组解析完了,没有碰到不符合 format 的内容或 EOF,并且 format 的内容没有匹配完
则 C 程序会再次在 scanf 中挂起(函数没有返回),控制会再次传递给命令行程序,以此类推
直到 format 的内容匹配完或遇到不匹配的值或 EOF 时,scanf 才会返回

返回时,标准输入中的读指针只移动到本次 format 匹配中的部分的后边,没有匹配中的部分仍然是未读状态
当再次调用 scanf 函数时会继续从之前剩下的部分读起
因此如果命令行输入一个不匹配的字符串,while (scanf("%lf", &v) != EOF) 会导致死循环
不断地因为不匹配而从函数返回,因为没有匹配,无法移动读指针,又因为每次标准输入都有未读的值,不会将控制传递给命令行程序

scanf 函数内部会逐个字符地循环 format
根据循环到的字符来决定对标准输入的读操作、解析逻辑、对变长参数表的指针移动与赋值、返回策略

函数将返回成功匹配的输入项的个数,但每个匹配项不一定都真的进行了赋值
比如有 2 个匹配项,但是 scanf 的赋值参数只有一个指针 scanf("%lf %lf", &v)
此时输入 "2.1 3.2",虽然只有 &v 被赋值,但 scanf 的返回值仍然是 2

如果到达文件的结尾,该函数将返回 EOF
注意,返回 EOF0 是不同的
0 表示下一个输入字符与格式串中的第一个格式说明不匹配

另外还有一个输入函数 sscanf,它用于从一个字符串(而不是标准输入)中读取字符序列:

int sscanf(char *string, char *format, arg1, arg2, ...)

它按照格式参数 format 中规定的格式扫描字符串 string
并把结果分别保存到 arg1arg2、… 这些参数中
这些参数必须是指针

格式串通常都包含转换说明,用于控制输入的转换
格式串可能包含下列部分:

  1. 空格或制表符,在处理过程中将被忽略
  2. 普通字符(不包括 %),用于匹配输入流中下一个非空白符字符
  3. 转换说明,依次由一个 %、一个可选的赋值禁止字符 *、一个可选的数值(指定最大字段宽度)、
    一个可选的 hlL 字符(指定目标对象的宽度)以及一个转换字符

转换说明控制下一个输入字段的转换
一般来说,转换结果存放在相应的参数指向的变量中
但是,如果转换说明中有赋值禁止字符 *,则跳过该输入字段,不进行赋值
输入字段定义为一个不包括空白符的字符串,其边界定义为到下一个空白符或达到指定的字段宽度
因为换行符也属于空白符(空白符包括空格符、 横向制表符、换行符、回车符、纵向制表符以及换页符)
所以 scanf 函数遇到行边界不会停下来,会继续按照规则读取并解析输入
如果没有匹配换行符的 format,则会简单跳过换行符,就像跳过其它空白符一样

转换字符指定对输入字段的解释
对应的参数必须是指针,这也是 C 语言通过值调用语义所要求的

转换字符输入数据参数类型
d十进制整数int *
i整数int *,可以是八进制(以 0 开头)或十六进制(以 0x 或 0X 开头)
o八进制整数(可以以 0 开头,也可以不以 0 开头)int *
u无符号十进制整数unsigned int *
x十六进制整数(可以 0x 或 0X 开头,也可以不以 0x 或 0X 开头)int *
c字符char *,将接下来的多个输入字符(默认为 1 个字符)存放到指定位置。该转换规范通常不跳过空白符。如果需要读入下一个非空白符,可以使用%1s
s字符串(不加引号)char *,指向一个足以存放该字符串(还包括尾部的字符’\0’)的字符数组。字符串的末尾将被添加一个结束符’\0’
e, f, g浮点数,它可以包括正负号(可选)、小数点(可选)及指数部分(可选)float *
%字符 %不进行任何赋值操作
表 7-2
scanf 函数的基本转换说明

转换说明 dioux 的前面可以加上字符 hl
前缀 h 表明参数表的相应参数是一个指向 short 类型而非 int 类型的指针
前缀 l 表明参数表的相应参数是一个指向 long 类型的指针
类似地,转换说明 efg 的前面也可以加上前缀 l,它表明参数表的相应参数是一个指向 double 类型而非 float 类型的指针

通过函数 scanf 执行输入转换来改写第 4 章中的简单计算器程序:

#include <stdio.h>

main() /* rudimentary calculator */ 
{ 
    double sum, v; 
    sum = 0; 
    while (scanf("%lf", &v) == 1) 
        printf("\t%.2f\n", sum += v); 
    return 0; 
}

读取包含 25 Dec 1988 日期格式的输入行:

int day, year;
char monthname[20];

scanf("%d %s %d", &day, monthname, &year);

因为数组名本身就是指针,所以,monthname 的前面没有取地址运算符 &

字符字面值也可以出现在 scanf 的格式串中,它们必须与输入中相同的字符匹配
因此可以使用下列 scanf 语句读入形如 mm/dd/yy 的日期数据:

int day, month, year;
scanf("%d/%d/%d", &month, &day, &year);

scanf 函数忽略格式串中的空格和制表符,此外,在读取输入值时,它将跳过空白符(空格、制表符、换行符等等)
如果要读取格式不固定的输入,最好每次读入一行,然后再用 sscanf 将合适的格式分离出来读入
例如,假定我们需要读取一些包含日期数据的输入行,日期的格式可能是上述任一种形式:

while (getline(line, sizeof(line)) > 0) { 
    if (sscanf(line, "%d %s %d", &day, monthname, &year) == 3) 
        printf("valid: %s\n", line); /* 25 Dec 1988 form */ 
    else if (sscanf(line, "%d/%d/%d", &month, &day, &year) == 3) 
        printf("valid: %s\n", line); /* mm/dd/yy form */ 
    else 
        printf("invalid: %s\n", line); /* invalid form */ 
}

scanf 函数可以和其它输入函数混合使用
无论调用哪个输入函数,下一个输入函数的调用将从 scanf 没有读取的第一个字符处开始读取数据

注意,scanfsscanf 函数的所有参数都必须是指针
最常见的错误是将输入语句写成 scanf("%d", n);
正确的形式应该为:scanf("%d", &n);
编译器在编译时一般检测不到这类错误


目录、参考文献

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值