我正在学习 C 语言,并试图了解 scanf 的工作原理。我无法理解一些术语:“输入项”、“初始子序列”、“匹配序列”等术语。 我正在阅读此https://pubs.opengroup.org/onlinepubs/9699919799/functions/scanf.html。它说:
输入项应定义为输入字节的最长序列(最多可达任何指定的最大字段宽度,可以根据转换说明符以字符或字节为单位来衡量),它是匹配序列的初始子序列。
假设格式为 i ,标准输入为4.5,其中%d表示初始子序列,匹配序列又是什么?输入项又是什么?
我认为输入项与该说明符相对应,即对于%d ,相应的符号是数字(可能是开头的 + - 符号),但随后它说:
除 % 转换说明符的情况外,输入项(或在 %n 转换说明的情况下,输入字节数)应转换为适合转换字符的类型。如果输入项不是匹配的序列,则转换说明的执行失败;
即输入项可能不对应,这意味着它不只由与说明符相对应的符号组成。
那么,您能给我解释一下这些术语吗?告诉我我是否在这里寻找 C 函数的文档?哪些网站是阅读 C 函数文档的最佳地点?
本文要处理的一个关键问题是,有时字符序列是否与转换模式匹配取决于尚未读取的字符。例如,输入文本“0x3”与模式匹配
%x
,但输入文本“0xy”不匹配。在我们读到“0x”的地方,“0x”本身与模式不匹配,我们不知道下一个字符是否会形成匹配序列。我们必须读取下一个字符才能找到答案。因此,继续读取字符时的规则
scanf
不能是“只要字符与目标模式匹配,就继续读取”。如果这是规则,我们将读取“0”,看到它与 的可能有效输入匹配%x
,然后读取“x”,看到“0x”不是 的有效输入%x
,然后停止。这不会起作用,因为它无法读取 的有效输入“0x3”%x
。规则必须是,
scanf
只要输入的字符能够匹配,则继续读取,如果接下来的字符完成匹配。从技术上来说,一种说法是,到目前为止读取的字符是匹配序列的初始子序列。的匹配序列
%d
可选为“-”,然后是一个或多个十进制数字。考虑匹配序列“123”和“-123”。“123”的初始子序列是空字符串、“1”、“12”和“123”。“-123”的初始子序列是空字符串、“-”、“-1”、“-12”和“-123”。因此,的匹配序列的初始子序列%d
可选为“-”,然后是零个或多个十进制数字。请注意,“-”是初始子序列,但不是匹配序列。更有趣的是考虑
%e
,其中“3.4e-5”是匹配序列,但“3.4”或“3.4ex”不是。现在scanf
必须读取两个字符,但不知道是否会有匹配。当scanf
读到“3.4”时,这是一个匹配序列,但只要字符形成初始子序列,它就必须继续。接下来我们有“3.4e”,它不再是匹配序列,但仍然是初始子序列。然后“3.4e-”也是一个初始子序列。有了“3.4e-5”,我们再次有一个匹配序列和一个初始子序列。scanf
必须继续读取。如果下一个字符是空格,“3.4e-5”不是初始子序列,因此空格被拒绝(并“放回”输入流,就像从未读取过一样),“3.4e-5”是输入项。此输入项是匹配序列,因此它被转换为float
。现在考虑读取“3.4ex”。如上所述,在“3.4e”处,
scanf
必须继续读取。它获取“x”并发现“3.4ex”不是初始子序列。它拒绝“x”并将其放回输入流。现在它已完成读取,“3.4e”是输入项。这不是匹配序列,因此匹配失败。scanf
不对此执行转换,并返回。请注意,对于“3.4ex”,如果我们可以放回两个字符而不是一个,我们就可以恢复为“3.4”,将其匹配
%e
,然后将其转换为float
。但是,C 标准不要求 I/O 流支持多个放回字符,并且scanf
指定只使用一个放回字符。这就是为什么scanf
指定读取直到不可能匹配,然后放回不匹配的字符,然后查看它读取的是否匹配。如果我们有更多级别的放回,scanf
可以读取直到不可能匹配,然后放回所有需要的字符以将输入减少为匹配序列,然后转换它。(请注意,正如评论中所讨论的,某些
scanf
实现不符合 C 标准的此规范,可能会放回多个字符。)没有哪个网站是最好的,也没有哪个小网站是最好的。C标准是阅读标准 C 库中函数的最权威的地方,但理解它们需要了解计算机科学理论和 C 语言的发展历史。对于这个问题
scanf
,关于解析、形式语言和有限状态机的理论可以说明计算机如何读取字符来解释它们。这些都是计算机科学教育研究的一部分,不是你能从狭隘的网站上轻易获得的东西。这有点令人困惑。
本质上,scanf 会读取转换说明符的字节,直到下一个字节不可能是该转换说明符应该读取的内容的一部分。然后它查看所读取的内容,这就是该转换说明符的输入项。(在底层,它可能使用 ungetc“取消读取”下一个字节。)
“匹配序列”是指与转换规范匹配的任何字符序列。它不限于实际出现在输入中的字符序列。例如,
0
和0xff
都是转换规范的匹配序列%x
。“匹配序列的初始子序列”是指可以作为匹配序列开头的任何字符序列。例如,
0
、0x
和0xff
都是0xff
转换规范 的匹配序列的初始子序列%x
。它们也是许多其他匹配序列的初始子序列,例如0xff00
或0xff1
。输入项是输入字符的最长序列,是匹配序列的初始子序列。例如,如果输入是 ,
0xgoose
并且 scanf 正在处理 的转换说明符%x
,则输入项是0x
。它不是0
,尽管0
是匹配序列,并且它不是任何比 更长的序列0x
,因为没有比 更长的序列可以作为匹配序列的开头。匹配序列
是任何字符序列,是所用转换说明符的有效表示。它不指实际输入。它指所有有效(又称匹配)序列。
匹配序列
%d
的例子有,等等。123456
987654
初始子序列
是从最左边的字符(即初始字符)开始的另一个序列的一部分(或全部)。
例如,对于序列
1234
,初始子序列为1
、和12
123
1234
输入项
是匹配序列的最长初始子序列。换句话说,就是将被转换并存储在提供的变量中的字符。
例如,对于序列
1234Z
和转换说明符,%d
匹配序列的最长初始子序列是1234
。测试工具可帮助评估与真实代码相比的答案。
示例输出:(请注意,此输出不一定符合 C 规范。)