强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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_stringstring -> unit输出字符串到 stdout
print_intint -> unit输出整数到 stdout
print_floatfloat -> unit输出浮点数到 stdout
print_endlinestring -> unit输出字符串并换行
print_charchar -> unit输出单个字符
print_newlineunit -> unit输出换行符
read_lineunit -> string从 stdin 读取一行
read_intunit -> int从 stdin 读取整数
read_floatunit -> 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 常用函数

函数类型说明
createint -> t创建缓冲区(初始容量)
add_stringt -> string -> unit追加字符串
add_chart -> char -> unit追加字符
add_bytest -> bytes -> unit追加字节
add_buffert -> t -> unit追加另一个缓冲区
contentst -> string获取当前内容(复制)
lengtht -> int当前长度
cleart -> unit清空内容
resett -> unit清空并释放内存
to_bytest -> 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_inUnix.openfile打开文件
inputUnix.read读取数据
outputUnix.write写入数据
close_inUnix.close关闭文件
seek_inUnix.lseek定位文件指针
in_channel_lengthUnix.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 ()

扩展阅读