OCaml 教程 / OCaml 输入输出基础
OCaml 输入输出基础
IO 操作是每个实际程序不可或缺的部分。本文系统讲解 OCaml 的 Channel 概念、标准输入输出、格式化打印、缓冲区以及 IO 异常处理。
Channel 概念
OCaml 的 IO 基于通道(Channel)抽象。通道分为输入通道(in_channel)和输出通道(out_channel)。
(* 标准通道 *)
(*
stdin : in_channel -- 标准输入
stdout : out_channel -- 标准输出
stderr : out_channel -- 标准错误
*)
let () =
(* 基础输出 *)
print_string "Hello, World!\n";
print_int 42;
print_newline ();
print_float 3.14;
print_newline ();
print_endline "This adds newline automatically"
标准输入输出函数
| 函数 | 类型 | 说明 |
|---|---|---|
print_string | string -> unit | 输出字符串到 stdout |
print_int | int -> unit | 输出整数到 stdout |
print_float | float -> unit | 输出浮点数到 stdout |
print_endline | string -> unit | 输出字符串并换行 |
print_char | char -> unit | 输出单个字符 |
print_newline | unit -> unit | 输出换行符 |
read_line | unit -> string | 从 stdin 读取一行 |
read_int | unit -> int | 从 stdin 读取整数 |
read_float | unit -> float | 从 stdin 读取浮点数 |
printf 和 Format
Printf 模块
Printf 提供 C 风格的格式化输出。
let () =
Printf.printf "String: %s\n" "hello";
Printf.printf "Int: %d\n" 42;
Printf.printf "Hex: %x\n" 255;
Printf.printf "Octal: %o\n" 255;
Printf.printf "Float: %f\n" 3.14;
Printf.printf "Scientific: %e\n" 3.14;
Printf.printf "Char: %c\n" 'A';
Printf.printf "Bool: %b\n" true;
Printf.printf "Width: [%10s]\n" "right";
Printf.printf "Left: [%-10s]\n" "left";
Printf.printf "Zero-pad: [%05d]\n" 42;
Printf.printf "Precision: [%.2f]\n" 3.14159
Printf 格式说明符
| 说明符 | 用途 | 示例 |
|---|---|---|
%s | 字符串 | "hello" |
%d | 整数 | 42 |
%x | 十六进制 | 0xFF |
%o | 八进制 | 0o377 |
%f | 浮点数 | 3.14 |
%e | 科学计数法 | 3.14e+00 |
%c | 字符 | 'A' |
%b | 布尔 | true |
%S | 带引号字符串 | "\"hello\"" |
%a | 自定义格式器 | pp fmt value |
%t | 带状态格式器 | fun fmt -> ... |
%! | 刷新缓冲区 | 无参数 |
%% | 字面 % | 无参数 |
sprintf 和 fprintf
(* sprintf: 格式化为字符串 *)
let greeting name age =
Printf.sprintf "Hello, %s! You are %d years old." name age
(* fprintf: 格式化到通道 *)
let log_error channel msg =
Printf.fprintf channel "[ERROR] %s\n%!" msg
(* eprintf: 格式化到 stderr *)
let warn msg =
Printf.eprintf "[WARN] %s\n%!" msg
let () =
let msg = greeting "Alice" 30 in
print_endline msg;
log_error stdout "Something went wrong";
warn "Low memory"
read_line / input_line
(* 从 stdin 读取一行 *)
let () =
Printf.printf "Enter your name: %!";
let name = read_line () in
Printf.printf "Hello, %s!\n" name
(* input_line 从指定通道读取 *)
let read_file_lines filename =
let ic = open_in filename in
let rec loop acc =
match input_line ic with
| line -> loop (line :: acc)
| exception End_of_file ->
close_in ic;
List.rev acc
in
loop []
(* 读取用户输入直到空行 *)
let read_until_empty () =
let rec loop acc =
let line = read_line () in
if line = "" then List.rev acc
else loop (line :: acc)
in
loop []
⚠️ 注意:
input_line在到达文件末尾时抛出End_of_file异常,而非返回空字符串。这是 OCaml 中常见的模式。
缓冲区 Buffer
Buffer 模块提供高效的可变字符串构建,避免频繁的字符串拼接。
let () =
let buf = Buffer.create 16 in (* 初始容量 16 字节 *)
Buffer.add_string buf "Hello";
Buffer.add_char buf ' ';
Buffer.add_string buf "World";
Buffer.add_char buf '\n';
(* 添加数字 *)
Buffer.add_string buf (string_of_int 42);
Buffer.add_char buf '\n';
(* 获取内容 *)
let content = Buffer.contents buf in
print_string content;
(* 清空缓冲区 *)
Buffer.clear buf;
Printf.printf "After clear: '%s'\n" (Buffer.contents buf);
(* 重用缓冲区 *)
Buffer.add_string buf "Reused!";
Printf.printf "Reused: '%s'\n" (Buffer.contents buf)
Buffer 常用函数
| 函数 | 类型 | 说明 |
|---|---|---|
create | int -> t | 创建缓冲区(初始容量) |
add_string | t -> string -> unit | 追加字符串 |
add_char | t -> char -> unit | 追加字符 |
add_bytes | t -> bytes -> unit | 追加字节 |
add_buffer | t -> t -> unit | 追加另一个缓冲区 |
contents | t -> string | 获取当前内容(复制) |
length | t -> int | 当前长度 |
clear | t -> unit | 清空内容 |
reset | t -> unit | 清空并释放内存 |
to_bytes | t -> bytes | 转为 bytes(不复制) |
实际场景:构建 CSV
let build_csv (header : string list) (rows : string list list) : string =
let buf = Buffer.create 256 in
(* 写入表头 *)
Buffer.add_string buf (String.concat "," header);
Buffer.add_char buf '\n';
(* 写入数据行 *)
List.iter (fun row ->
Buffer.add_string buf (String.concat "," row);
Buffer.add_char buf '\n'
) rows;
Buffer.contents buf
let () =
let header = ["Name"; "Age"; "City"] in
let rows = [
["Alice"; "30"; "Beijing"];
["Bob"; "25"; "Shanghai"];
["Charlie"; "35"; "Shenzhen"];
] in
print_string (build_csv header rows)
input_char / input_byte
(* 逐字符读取 *)
let read_chars filename =
let ic = open_in filename in
let buf = Buffer.create 64 in
let rec loop () =
match input_char ic with
| c ->
Buffer.add_char buf c;
loop ()
| exception End_of_file ->
close_in ic;
Buffer.contents buf
in
loop ()
(* 读取固定字节数 *)
let read_n_bytes ic n =
let bytes = Bytes.create n in
let rec read_loop offset remaining =
if remaining = 0 then bytes
else
let n = input ic bytes offset remaining in
if n = 0 then raise End_of_file
else read_loop (offset + n) (remaining - n)
in
read_loop 0 n
IO 异常处理
let safe_read_file filename =
try
let ic = open_in filename in
let content =
let buf = Buffer.create 256 in
let rec loop () =
match input_line ic with
| line ->
Buffer.add_string buf line;
Buffer.add_char buf '\n';
loop ()
| exception End_of_file -> Buffer.contents buf
in
loop ()
in
close_in ic;
Ok content
with
| Sys_error msg -> Error (Printf.sprintf "IO Error: %s" msg)
| exn -> Error (Printf.sprintf "Unknown error: %s" (Printexc.to_string exn))
(* 更安全的方式:使用保护器 *)
let with_open_in filename f =
let ic = open_in filename in
Fun.protect ~finally:(fun () -> close_in ic) (fun () -> f ic)
let read_all filename =
with_open_in filename (fun ic ->
let buf = Buffer.create 256 in
let rec loop () =
match input_line ic with
| line ->
Buffer.add_string buf line;
Buffer.add_char buf '\n';
loop ()
| exception End_of_file -> Buffer.contents buf
in
loop ()
)
let () =
match safe_read_file "test.txt" with
| Ok content -> print_string content
| Error msg -> Printf.eprintf "Error: %s\n" msg
💡 提示:
Fun.protect(OCaml 4.08+)是异常安全的资源管理最佳实践。它确保finally无论是否发生异常都会执行。
Unix 库 IO 扩展
Unix 库提供了更底层的 IO 操作。
(* 需要链接 unix 库:ocamlfind ocamlopt -package unix *)
(*
open Unix
let read_with_unix filename =
let fd = openfile filename [O_RDONLY] 0o644 in
let buf = Bytes.create 1024 in
let n = read fd buf 0 1024 in
close fd;
Bytes.sub_string buf 0 n
let write_with_unix filename content =
let fd = openfile filename [O_WRONLY; O_CREAT; O_TRUNC] 0o644 in
let _ = write_substring fd content 0 (String.length content) in
close fd
*)
Unix IO 函数对照
| 标准库 | Unix 库 | 说明 |
|---|---|---|
open_in | Unix.openfile | 打开文件 |
input | Unix.read | 读取数据 |
output | Unix.write | 写入数据 |
close_in | Unix.close | 关闭文件 |
seek_in | Unix.lseek | 定位文件指针 |
in_channel_length | Unix.fstat | 获取文件大小 |
实际业务场景:交互式 CLI
let prompt message =
Printf.printf "%s: %!" message;
read_line ()
let confirm message =
Printf.printf "%s [y/N]: %!" message;
match read_line () with
| "y" | "Y" | "yes" | "Yes" -> true
| _ -> false
let read_password () =
Printf.printf "Password: %!";
(* 简化版:实际应用中应隐藏输入 *)
read_line ()
let interactive_setup () =
let name = prompt "Enter your name" in
let email = prompt "Enter your email" in
let password = read_password () in
Printf.printf "\nRegistration Summary:\n";
Printf.printf " Name: %s\n" name;
Printf.printf " Email: %s\n" email;
if confirm "Proceed with registration?" then begin
Printf.printf "Registration complete!\n"
end else begin
Printf.printf "Registration cancelled.\n"
end
let () = interactive_setup ()