Go fmt 输出与格式化
fmt 输出函数由两个独立维度组合——写到哪(stdout 还是任意 io.Writer)与怎么格式化(默认还是占位符动词);%w 建链、%v 断链,错误分流走 os.Stderr。
#type / concept
#status / growing
#tech / dev
#resource / go
#package / fmt
[!info] 关联笔记
Go fmt 输出与格式化
这个概念为什么会出现
打印到屏幕、写错误、把错误包进链、把数据写进任意流——这些事如果每个函数都自己撸一遍字节缓冲,代码又碎又难测。Go 用 fmt 包统一了「格式化 I/O」:输出到哪里、怎么格式化两个维度正交,组合出一族函数。
核心动机是可注入 + 可测:正是因为 Fprintln(w io.Writer, ...) 的第一个参数是接口,测试才能把 bytes.Buffer 塞进去断言输出,而不是真去碰 stdout。
一句话定义
fmt 把「输出目标(stdout / 任意 Writer)」与「格式化方式(默认 / 占位符动词)」两个正交维度组合成一组函数;ln 后缀表示自动补换行,f 后缀表示用占位符动词。
核心机制:两个独立维度
不要背六个孤立函数,按两维理解:
- 写到哪
- 不带
F:写标准输出os.Stdout(如Println) - 带
F(File/Writer):写你传进去的任意io.Writer(如Fprintln(w, ...))
- 不带
- 怎么格式化
- 不带
f:默认格式(等价于%v,参数间自动加空格)——Print/Println - 带
f(format):按占位符模板格式化——Printf/Fprintf,需自己写\n
- 不带
组合矩阵:
| 函数 | 写到 | 格式 | 末尾换行 |
|---|---|---|---|
Print | stdout | 默认 | 否 |
Println | stdout | 默认,参数间加空格 | 是 |
Printf | stdout | 占位符模板 | 否(自写 \n) |
Fprint | 指定 Writer | 默认 | 否 |
Fprintln | 指定 Writer | 默认,加空格 | 是 |
Fprintf | 指定 Writer | 占位符模板 | 否 |
Sprint/Sprintln/Sprintf 同构,只是返回 string 而非写出。
占位符动词(verbs)
Go 的动词直接继承自 C 的 printf 家族,%d 就是十进制整数:
| 动词 | 含义 |
|---|---|
%d | 十进制整数 |
%s | 字符串 |
%f | 浮点(定点) |
%v | 值的默认格式,通吃 |
%T | 值的类型 |
%#v | Go 语法形式(调试神器) |
%w | 仅 Errorf 用,把错误包进链 |
宽度/精度同 C:%5d、%.2f、%-5s(左对齐)。%v 是 Go 比 C 方便处——不必纠结该用 %d 还是 %s。
[!abstract] 关键区别:
%w建链,%v断链fmt.Errorf("scan: %w", err)把err包进错误链,errors.Is/As能顺着Unwrap一路比对;用%v只把错误拼进字符串,链断,再也比对不到原始错误。要保留链用%w。
最小可运行示例
// 写到 stdout,默认格式,带换行
fmt.Println("line count", n) // line count 2
// 写到 stdout,占位符,无自动换行
fmt.Printf("count=%d\n", n) // count=2
// 写到任意 Writer(这里是 stderr),可测
fmt.Fprintln(os.Stderr, "err:", err) // 错误分流到 stderr
// 占位符建链
err2 := fmt.Errorf("read: %w", err)
fmt.Println(errors.Is(err2, err)) // true
边界与易混淆点
Printf不自动换行——忘记\n输出会粘行;想省事用Println。Fprintln的F是 Writer 不是 flush——Fprintln(os.Stderr, ...)只是换个出口写,和 stdout 平级。- 错误走
os.Stderr是 Unix 管道约定——正常结果走 stdout,错误走 stderr,这样prog file | sort只传递正常数字,错误提示不污染数据流。 ln在不同语境是不同东西——GoPrintln的ln= line(换行);Unix 命令ln= link(链接),别混。- 占位符与实参类型不匹配——Go 不 panic,打印
%!d(string=hi)友好提示(比 C 安全),但go vet能提前揪出明显错配。
设计动机
- 正交组合:目标与格式解耦,6 个函数覆盖绝大多数场景,不必各写一套。
- 可注入:
F系列认io.Writer,业务函数得以脱离 stdout、直接测。 - 错误即值:
Errorf("%w")让错误像数据一样被包裹、被比对,而非异常抛出。
自测题
fmt.Fprintln(os.Stderr, x)比fmt.Println(x)多了什么语义?- 为什么测试里用
Fprintln(&bytes.Buffer, ...)而不是Println? fmt.Errorf("%v", err)与%w在errors.Is上的结果有何不同?
答案
- 写向 stderr 而非 stdout,且 F 系列可写任意 Writer;错误分流不污染管道数据。
Println写固定 stdout,测试无法捕获断言;Fprintln写进内存 Buffer,可直接out.String()比对。%v断链,比对不到原始错误;%w建链,errors.Is为 true。
延伸阅读
| 资料 | 类型 | 支撑 |
|---|---|---|
| Package fmt | 标准库 | 输出与格式化 |
| [[go-error-wrapping | 错误包装]] | 本库 |
| [[go-io-reader-writer | Reader/Writer]] | 本库 |