← [ BACK_TO_NOTES ]

Go fmt 输出与格式化

fmt 输出函数由两个独立维度组合——写到哪(stdout 还是任意 io.Writer)与怎么格式化(默认还是占位符动词);%w 建链、%v 断链,错误分流走 os.Stderr。

#concept #growing #dev #go #fmt

[!info] related notes

Go fmt 输出与格式化

这个概念为什么会出现

打印到屏幕、写错误、把错误包进链、把数据写进任意流——这些事如果每个函数都自己撸一遍字节缓冲,代码又碎又难测。Go 用 fmt 包统一了「格式化 I/O」:输出到哪里、怎么格式化两个维度正交,组合出一族函数。

核心动机是可注入 + 可测:正是因为 Fprintln(w io.Writer, ...) 的第一个参数是接口,测试才能把 bytes.Buffer 塞进去断言输出,而不是真去碰 stdout。

一句话定义

[!note] 命名边界 本文的 fmt 是 Go 标准库包;代码格式化工具 gofmt 属于 Go 静态分析与代码检查。避免使用含混的 Go fmt alias 把二者合并。

fmt 把「输出目标(stdout / 任意 Writer)」与「格式化方式(默认 / 占位符动词)」两个正交维度组合成一组函数;ln 后缀表示自动补换行,f 后缀表示用占位符动词。

核心机制:两个独立维度

不要背六个孤立函数,按两维理解:

  1. 写到哪
    • 不带 F:写标准输出 os.Stdout(如 Println)
    • 带 F(File/Writer):写你传进去的任意 io.Writer(如 Fprintln(w, ...))
  2. 怎么格式化
    • 不带 f:默认格式;Print/Fprint/Sprint 只在相邻两个参数都不是字符串时加空格,Println 系列总在参数间加空格并补换行
    • 带 f(format):按占位符模板格式化——Printf/Fprintf,需自己写 \n

组合矩阵:

函数写到格式末尾换行
Printstdout默认否
Printlnstdout默认,参数间加空格是
Printfstdout占位符模板否(自写 \n)
Fprint指定 Writer默认否
Fprintln指定 Writer默认,加空格是
Fprintf指定 Writer占位符模板否

Sprint/Sprintln/Sprintf 同构,只是返回 string 而非写出。

占位符动词(verbs)

Go 的格式化形式与 printf 家族相似,但有自己的类型和动词规则;%d 表示十进制整数:

动词含义
%d十进制整数
%s字符串
%f浮点(定点)
%v值的默认格式,通吃
%T值的类型
%#vGo 语法形式(调试神器)
%w仅 Errorf 用,把错误包进链

例如 %5d、%.2f、%-5s(左对齐)。宽度与精度通常按 rune 计量,具体动词有例外(如字符串 %x 的精度按字节),不是终端显示列宽,也不能照搬 C 的全部规则。%v 是 Go 比 C 方便处——不必纠结该用 %d 还是 %s。

[!abstract] 关键区别:%w 建链,%v 断链 fmt.Errorf("scan: %w", err) 把 err 包进错误链,errors.Is/As 能顺着 Unwrap 一路比对;用 %v 只把错误拼进字符串,链断,再也比对不到原始错误。要保留链用 %w。

最小可运行示例

保存为新模块中的 main.go 后运行 go run .:

package main

import (
    "errors"
    "fmt"
    "os"
)

func main() {
    n := 2
    err := errors.New("demo read failure")
    fmt.Println("line count", n)        // stdout: line count 2
    fmt.Printf("count=%d\n", n)         // stdout: count=2
    fmt.Fprintln(os.Stderr, "err:", err) // stderr: 演示错误
    wrapped := fmt.Errorf("read: %w", err)
    fmt.Println(errors.Is(wrapped, err)) // stdout: true
}

输出函数返回写入字节数和 error;本例为展示格式忽略返回值,业务输出不可丢失时应处理写失败。写入 bufio.Writer 也不会自动 Flush。

边界与易混淆点

  1. Printf 不自动换行——忘记 \n 输出会粘行;想省事用 Println。
  2. Fprintln 的 F 是 Writer 不是 flush——Fprintln(os.Stderr, ...) 只是换个出口写,和 stdout 平级。
  3. 错误走 os.Stderr 是 Unix 管道约定——正常结果走 stdout,错误走 stderr,这样 prog file | sort 只传递正常数字,错误提示不污染数据流。
  4. ln 在不同语境是不同东西——Go Println 的 ln = line(换行);Unix 命令 ln = link(链接),别混。
  5. 占位符与实参类型不匹配——Go 不 panic,打印 %!d(string=hi) 友好提示(比 C 安全),但 go vet 能提前揪出明显错配。

设计动机

  1. 正交组合:目标与格式解耦,6 个函数覆盖绝大多数场景,不必各写一套。
  2. 可注入:F 系列认 io.Writer,业务函数得以脱离 stdout、直接测。
  3. 错误即值:Errorf("%w") 让错误像数据一样被包裹、被比对,而非异常抛出。

自测题

  1. fmt.Fprintln(os.Stderr, x) 比 fmt.Println(x) 多了什么语义?
  2. 为什么测试里用 Fprintln(&bytes.Buffer, ...) 而不是 Println?
  3. fmt.Errorf("%v", err) 与 %w 在 errors.Is 上的结果有何不同?
答案
  1. 写向 stderr 而非 stdout,且 F 系列可写任意 Writer;错误分流不污染管道数据。
  2. Println 的 stdout 可以通过重定向或子进程捕获,但需额外处理全局/进程边界;Fprintln 注入内存 Buffer,可直接断言且更利于并行测试。
  3. %v 断链,比对不到原始错误;%w 建链,errors.Is 为 true。

延伸阅读

资料类型支撑
Package fmt标准库输出与格式化
[[go-error-wrapping错误包装]]本库
[[go-io-reader-writerReader/Writer]]本库
创建于 2026/7/28 更新于 2026/10/6
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本