根据标准(C17草案,6.4.4.2¶1),
2E0
(2.0)和.5
(0.5)是有效的浮动常数,而2E
和.
不是。
但是,
scanf
将 2E
解析为 2.0,但无法将 .
解析为任何内容:
#include <stdio.h>
int main(void) {
float fl;
char c;
printf("Please enter a floating-point number: ");
if (scanf("%f", &fl) == 1)
printf("<%.2f>\n", fl);
if (scanf("%c", &c) == 1)
printf("[%c]\n", c);
return 0;
}
使用 GCC 12.2.0(Windows 上的 MinGW)生成上述代码:
Please enter a floating-point number: 2Eq
<2.00>
[q]
Please enter a floating-point number: .q
[q]
这里,
q
用作虚拟字符,以演示先前对scanf
的调用消耗了多少输入缓冲区。
(让我们忽略这样一个事实,即不清楚浮点数
.
“应该”代表什么:0 或 1。)
让我们试着理解这一点。与此相关的似乎是标准中的以下条款(C17 草案,7.21.6.2 ¶9):
从流中读取输入项,除非规范包含 n 说明符。输入项被定义为最长的输入字符序列,该序列不超过任何指定的字段宽度,并且是匹配输入序列或其前缀。291) 输入项之后的第一个字符(如果有)仍未阅读。如果输入项的长度为零,则指令执行失败;这种情况是匹配失败,除非文件结尾、编码错误或读取错误阻止了流的输入,在这种情况下,它是输入失败。
291)
最多将一个输入字符推回到输入流上。因此,一些fscanf
、strtod
等可以接受的序列,对于strtol
来说是不可接受的。fscanf
据我所知,该标准关于
strtod
和朋友(此处:strtof
)与我的问题最相关的条款是 7.22.1.3 ¶3(此处未复制),从中可以得出 2E0
(2.0) 和 .5
(0.5) 是有效的“主题序列”(在 7.22.1.3 ¶2 的意义上),而 2E
和 .
则不是。
如果我正确理解第 7.21.6.2 节 ¶9 条,则只有 0 长度的输入项才相当于“匹配失败”或“输入失败”。因为 2E
和 .
都是匹配输入序列
的有效前缀(尽管不是完整的
匹配输入序列),所以两者都不是匹配失败或输入失败。 因此我们可以问:为什么
scanf
将2E
解析为浮点数,而不是
.
?
这可能与“匹配输入序列的前缀”定义的微妙之处有关。
第 7.22.1.3 ¶3 条(关于 strtod
strtof
strtold
)周围的细节也可能是相关的,因为它们与 1
和朋友的 scanf
的推回限制有关。
我相信以下代码说明了“匹配输入序列的前缀”的概念:#include <stdio.h>
int main(void) {
char p[5] = "pppp", q[5] = "qqqq";
int i;
i = sscanf("ABCD", "%2c%4c", p, q);
printf("<%s>\n", p); /* <ABpp> */
printf("<%s>\n", q); /* <CDqq> */
printf("%d\n", i); /* 2 */
return 0;
}
此处,尽管
%4c
/
q
的匹配需要正好 4 个字符(C17 草案,7.21.6.2 ¶12,项目
c
;此处未复制有关多字节字符的不相关脚注)
匹配字段宽度所指定数量的字符序列(如果指令中不存在字段宽度,则为 1)。[fn]
是有效的“匹配输入序列的前缀”,因此该代码不会导致匹配失败或输入失败。 (鉴于赋值可能比给定的字段宽度短,我发现标准使用“完全”一词令人困惑。)
CD
我发现了2个类似的问题(此处列出,排名不分先后):
“scanf”与不完整的指数部分有什么关系?
%Nc
的例子有更广泛的覆盖面。)
如果我正确理解第 7.21.6.2 节 ¶9 条,则只有 0 长度的输入项才相当于
输入失败...如果输入项不是匹配序列,则指令执行失败:此条件为匹配失败...。 7.21.6.2 9 并不是唯一指定匹配失败的段落。第 10 段说:
“。”是匹配输入序列的前缀,因此它被扫描(消费,从流中删除),但它不是匹配的输入序列,因此存在匹配失败。