module Scanf:sig
..end
格式化输入函数。
模块 Scanf
提供格式化输入函数或扫描器。
格式化输入函数可以从任何类型的输入中读取,包括字符串、文件或任何可以返回字符的内容。更通用的字符源称为格式化输入通道(或扫描缓冲区),其类型为 Scanf.Scanning.in_channel
。更通用的格式化输入函数从任何扫描缓冲区读取,名为 bscanf
。
一般来说,格式化输入函数有 3 个参数
因此,对格式化输入函数 Scanf.bscanf
的典型调用为 bscanf ic fmt f
,其中
ic
是字符源(通常是类型为 Scanf.Scanning.in_channel
的格式化输入通道),f
是一个函数,其参数数量与根据 fmt
在输入中读取的值的数量相同。如上所述,表达式 bscanf ic "%d" f
从字符源 ic
读取一个十进制整数 n
,并返回 f n
。
例如,
stdin
作为字符源(Scanf.Scanning.stdin
是从标准输入读取的预定义格式化输入通道),f
定义为 let f x = x + 1
,那么 bscanf Scanning.stdin "%d" f
从标准输入读取一个整数 n
,并返回 f n
(即 n + 1
)。因此,如果我们计算 bscanf stdin "%d" f
,然后在键盘上输入 41
,则我们得到的结果为 42
。
OCaml 的扫描功能让人联想到相应的 C 功能。但是,它也存在很大差异,更简单,但功能更强大:格式化输入函数是高阶函数,参数传递机制只是常规的函数应用,而不是命令式语言中常见的基于变量分配的机制;OCaml 格式字符串还具有有用的补充,可以轻松定义复杂的标记;正如函数式编程语言中所预期的那样,格式化输入函数还支持多态性,特别是与多态用户定义的扫描器进行任意交互。此外,OCaml 格式化输入功能在编译时完全类型检查。
未同步访问
对 Scanf.Scanning.in_channel
的未同步访问可能会导致 Scanf.Scanning.in_channel
状态无效。因此,对 Scanf.Scanning.in_channel
的并发访问必须同步(例如,使用 Mutex.t
)。
module Scanning:sig
..end
type('a, 'b, 'c, 'd)
scanner =('a, Scanning.in_channel, 'b, 'c, 'a -> 'd, 'd) format6 -> 'c
格式化输入扫描器的类型:('a, 'b, 'c, 'd) scanner
是从某个格式化输入通道根据某个格式字符串读取的格式化输入函数的类型;更准确地说,如果 scan
是某个格式化输入函数,那么 scan
将
ic fmt ff
应用于格式字符串 fmt
指定的所有参数,当 scan
从 Scanf.Scanning.in_channel
格式化输入通道 ic
读取这些参数时。
例如,下面的 Scanf.scanf
函数的类型为 ('a, 'b, 'c, 'd) scanner
,因为它是一个从 Scanf.Scanning.stdin
读取的格式化输入函数:scanf fmt f
将 f
应用于 fmt
指定的参数,如预期的那样,从 stdin
读取这些参数。
如果格式 fmt
包含一些 %r
指示,则必须在接收函数 f
之前提供相应的格式化输入函数。例如,如果 read_elem
是类型为 t
的值的输入函数,那么 bscanf ic "%r;" read_elem f
读取一个类型为 t
的值 v
,后面跟着一个 ';'
字符,并返回 f v
。
type('a, 'b, 'c, 'd)
scanner_opt =('a, Scanning.in_channel, 'b, 'c, 'a -> 'd option, 'd) format6 ->
'c
exception Scan_failure of string
当无法根据格式字符串规范读取输入时,格式化输入函数通常会引发异常 Scan_failure
。
val bscanf : Scanning.in_channel -> ('a, 'b, 'c, 'd) scanner
bscanf ic fmt r1 ... rN f
从 Scanf.Scanning.in_channel
格式化输入通道 ic
读取字符,并根据格式字符串 fmt
将它们转换为值。作为最后一步,接收函数 f
应用于读取的值,并给出 bscanf
调用的结果。
例如,如果 f
是函数 fun s i -> i + 1
,那么 Scanf.sscanf "x = 1" "%s = %i" f
返回 2
。
参数 r1
到 rN
是用户定义的输入函数,用于读取与格式字符串中指定的 %r
转换相对应的参数。
val bscanf_opt : Scanning.in_channel -> ('a, 'b, 'c, 'd) scanner_opt
与 Scanf.bscanf
相同,但在扫描失败的情况下返回 None
。
格式字符串是一个字符字符串,其中包含三种类型的对象
f
读取和转换一个参数(请参见 格式字符串中的转换说明),如上所述,格式字符串中的普通字符只是与输入中的下一个字符匹配;但是,有两个字符是该规则的特殊例外:空格字符 (' '
或 ASCII 码 32) 和换行符 ('\n'
或 ASCII 码 10)。空格不匹配单个空格字符,而是匹配输入中的任何数量的“空白”。更准确地说,格式字符串中的空格匹配任何数量的制表符、空格、换行符和回车符。类似地,格式字符串中的换行符匹配单个换行符或回车符后跟换行符。
匹配任何数量的空白,格式字符串中的空格也不匹配任何空白;因此,调用 bscanf ib
成功并返回
"Price = %d $" (fun p -> p)1
,当读取具有各种空白的输入时,例如 Price = 1 $
、Price = 1 $
,甚至 Price=1$
。
转换说明由 %
字符、可选的标志、可选的字段宽度以及一个或两个转换字符组成。
转换字符及其含义为
d
:读取一个可选带符号的十进制整数 (0-9
+)。i
:读取一个可选带符号的整数(理解十进制 (0-9
+)、十六进制 (0x[0-9a-f]+
和 0X[0-9A-F]+
)、八进制 (0o[0-7]+
) 和二进制 (0b[0-1]+
) 表示法的常用输入约定)。u
:读取一个无符号的十进制整数。x
或 X
:读取一个无符号的十六进制整数 ([0-9a-fA-F]+
)。o
:读取一个无符号的八进制整数 ([0-7]+
)。s
:读取一个尽可能扩展的字符串参数,直到满足以下边界条件S
:读取一个分隔符字符串参数(分隔符和特殊转义字符遵循 OCaml 的词法约定)。c
:读取单个字符。要测试当前输入字符而不读取它,请指定一个空字段宽度,即使用规范 %0c
。如果字段宽度规范大于 1,则引发 Invalid_argument
。C
:读取单个分隔符字符(分隔符和特殊转义字符遵循 OCaml 的词法约定)。f
, e
, E
, g
, G
: 以十进制表示法读取可选符号浮点数,格式为 dddd.ddd
e/E+-dd
。h
, H
: 以十六进制表示法读取可选符号浮点数。F
: 按照 OCaml 的词法约定读取浮点数(因此,如果省略指数部分,则必须包含小数点)。B
: 读取布尔值参数(true
或 false
)。b
: 读取布尔值参数(为了向后兼容;不要在新的程序中使用)。ld
, li
, lu
, lx
, lX
, lo
: 读取格式为 int32
的参数,第二个字母指定了用于普通整数的格式。nd
, ni
, nu
, nx
, nX
, no
: 读取格式为 nativeint
的参数,第二个字母指定了用于普通整数的格式。Ld
, Li
, Lu
, Lx
, LX
, Lo
: 读取格式为 int64
的参数,第二个字母指定了用于普通整数的格式。[ range ]
: 读取与字符范围 range
中提到的一个或多个字符匹配的字符(如果范围以 ^
开头,则是不匹配的字符)。读取一个 string
,如果下一个输入字符与范围不匹配,则可以为空字符串。从 c1
到 c2
(包括)的字符集用 c1-c2
表示。因此,%[0-9]
返回一个表示十进制数字的字符串,如果未找到十进制数字,则返回空字符串;类似地,%[0-9a-f]
返回一个十六进制数字字符串。如果在范围内出现右方括号,则必须出现在范围的第一个字符(或在范围取反的情况下,在 ^
之后);因此,[]]
匹配一个 ]
字符,而 [^]]
匹配除 ]
之外的任何字符。使用 %%
和 %@
将 %
或 @
包含在范围内。r
: 用户定义的读取器。使用下一个 ri
格式的输入函数并将其应用于扫描缓冲区 ib
以读取下一个参数。因此,输入函数 ri
的类型必须为 Scanning.in_channel -> 'a
,读取的参数的类型为 'a
。{ fmt %}
: 读取格式字符串参数。读取的格式字符串必须与格式字符串规范 fmt
的类型相同。例如,"%{ %i %}"
读取任何可以读取类型为 int
的值的格式字符串;因此,如果 s
是字符串 "fmt:\"number is %u\""
,则 Scanf.sscanf s "fmt: %{%i%}"
成功并返回格式字符串 "number is %u"
。( fmt %)
: 扫描子格式替换。在输入中读取格式字符串 rf
,然后继续使用 rf
扫描,而不是使用 fmt
扫描。格式字符串 rf
必须与它替换的格式字符串规范 fmt
的类型相同。例如,"%( %i %)"
读取任何可以读取类型为 int
的值的格式字符串。转换返回读取的格式字符串 rf
,然后使用 rf
读取的值。因此,如果 s
是字符串 "\"%4d\"1234.00"
,则 Scanf.sscanf s "%(%i%)" (fun fmt i -> fmt, i)
评估为 ("%4d", 1234)
。这种行为不仅仅是格式替换,因为转换返回读取的格式字符串作为附加参数。如果您需要纯粹的格式替换,请使用特殊标志 _
丢弃多余的参数:转换 %_( fmt %)
读取格式字符串 rf
,然后行为与格式字符串 rf
相同。因此,如果 s
是字符串 "\"%4d\"1234.00"
,则 Scanf.sscanf s "%_(%i%)"
仅仅等同于 Scanf.sscanf "1234.00" "%4d"
。l
: 返回到目前为止读取的行数。n
: 返回到目前为止读取的字符数。N
或 L
: 返回到目前为止读取的标记数。!
: 匹配输入结束条件。%
: 匹配输入中的一个 %
字符。@
: 匹配输入中的一个 @
字符。,
: 不做任何操作。在引入转换的 %
字符之后,可能会有特殊标志 _
:后面的转换照常进行,但结果值会被丢弃。例如,如果 f
是函数 fun i -> i + 1
,而 s
是字符串 "x = 1"
,则 Scanf.sscanf s "%_s = %i" f
返回 2
。
字段宽度由一个可选的整数文字组成,该文字表示要读取的标记的最大宽度。例如,%6d
读取一个整数,最多有 6 位十进制数字;%4f
读取一个最多有 4 个字符的浮点数;而 %8[\000-\255]
返回接下来的 8 个字符(如果输入中可用字符少于 8 个,则返回所有可用字符)。
注释
%s
转换也会始终成功:在这种情况下,它只是返回 ""
。'_'
字符可以出现在数字中(这与通常的 OCaml 词法约定类似)。如果需要更严格的扫描,请使用范围转换功能,而不是数字转换。scanf
功能并非用于繁重的词法分析和解析。如果它看起来不够表达您的需求,则存在几种替代方案:正则表达式(模块 Str
)、流解析器、ocamllex
生成的词法分析器、ocamlyacc
生成的解析器。扫描指示出现在字符串转换 %s
和 %[ range ]
之后,用于分隔标记的结束。扫描指示由一个 @
字符开头,后面跟着一些普通字符 c
。这意味着字符串标记应该在下一个匹配的 c
(该字符被跳过)之前结束。如果没有遇到 c
字符,则字符串标记会尽可能地扩展。例如,"%s@\t"
读取到下一个制表符或输入结束之前的字符串。如果在格式字符串中的其他任何地方出现 @
字符,则将其视为普通字符。
注释
%
和 @
字符必须使用 %%
和 %@
转义;此规则仍然适用于范围规范和扫描指示。例如,格式 "%s@%%"
读取到下一个 %
字符之前的字符串,而格式 "%s@%@"
读取到下一个 @
之前的字符串。Scanf
格式字符串的语法中引入了一些细微的差异,与用于 Printf
模块的那些语法相比。但是,扫描指示类似于 Format
模块中使用的那些;因此,在生成要由 Scanf.bscanf
扫描的格式化文本时,最好使用 Format
模块的打印函数(或者,如果您需要使用 Printf
中的函数,请禁止或仔细检查包含 '@'
字符的格式字符串)。当输入无法根据格式字符串进行读取时,扫描器可能会引发以下异常
Scanf.Scan_failure
。Failure
。End_of_file
。Invalid_argument
。注释
%s
转换永远不会引发异常 End_of_file
:如果遇到输入结束,转换将成功并简单地返回到目前为止读取的字符,或者如果从未读取任何字符,则返回 ""
。val sscanf : string -> ('a, 'b, 'c, 'd) scanner
与 Scanf.bscanf
相同,但从给定字符串中读取。
val sscanf_opt : string -> ('a, 'b, 'c, 'd) scanner_opt
与 Scanf.sscanf
相同,但如果扫描失败,则返回 None
。
val scanf : ('a, 'b, 'c, 'd) scanner
与 Scanf.bscanf
相同,但从预定义的格式化输入通道 Scanf.Scanning.stdin
读取,该通道连接到 stdin
。
val scanf_opt : ('a, 'b, 'c, 'd) scanner_opt
与 Scanf.scanf
相同,但在扫描失败的情况下返回 None
。
val kscanf : Scanning.in_channel ->
(Scanning.in_channel -> exn -> 'd) -> ('a, 'b, 'c, 'd) scanner
与 Scanf.bscanf
相同,但接受一个额外的函数参数 ef
,在发生错误时调用:如果扫描过程或某些转换失败,扫描函数将中止并调用错误处理函数 ef
,并将格式化输入通道和中止扫描过程的异常作为参数传递。
val ksscanf : string ->
(Scanning.in_channel -> exn -> 'd) -> ('a, 'b, 'c, 'd) scanner
与 Scanf.kscanf
相同,但从给定的字符串读取。
val bscanf_format : Scanning.in_channel ->
('a, 'b, 'c, 'd, 'e, 'f) format6 ->
(('a, 'b, 'c, 'd, 'e, 'f) format6 -> 'g) -> 'g
bscanf_format ic fmt f
从格式化输入通道 ic
读取一个格式字符串标记,根据给定的格式字符串 fmt
,并将 f
应用于生成的格式字符串值。
Scan_failure
如果读取的格式字符串值与 fmt
的类型不一致。val sscanf_format : string ->
('a, 'b, 'c, 'd, 'e, 'f) format6 ->
(('a, 'b, 'c, 'd, 'e, 'f) format6 -> 'g) -> 'g
与 Scanf.bscanf_format
相同,但从给定的字符串读取。
val format_from_string : string ->
('a, 'b, 'c, 'd, 'e, 'f) format6 ->
('a, 'b, 'c, 'd, 'e, 'f) format6
format_from_string s fmt
将字符串参数转换为格式字符串,根据给定的格式字符串 fmt
。
Scan_failure
如果 s
(作为格式字符串)的类型与 fmt
的类型不一致。val unescaped : string -> string
unescaped s
返回一个 s
的副本,其中转义序列(根据 OCaml 的词法约定)被替换为相应的特殊字符。更准确地说,Scanf.unescaped
具有以下属性:对于所有字符串 s
,Scanf.unescaped (String.escaped s) = s
。
始终返回参数的副本,即使参数中没有转义序列。
Scan_failure
如果 s
没有正确转义(即 s
包含无效的转义序列或未正确转义的特殊字符)。例如,Scanf.unescaped "\""
会失败。