Go fmt 输出与格式化
fmt 输出函数由两个独立维度组合——写到哪(stdout 还是任意 io.Writer)与怎么格式化(默认还是占位符动词);%w 建链、%v 断链,错误分流走 os.Stderr。
[!info] related notes
- 所属 MOC:标准库
- 前置概念:Reader/Writer(Fprintln 写任意 Writer)、错误包装(%w 建链)
- 并列/易混淆:字符串与字面量、if 初始化子句、Go 静态分析与代码检查(
gofmt工具)
Go fmt 输出与格式化
这个概念为什么会出现
打印到屏幕、写错误、把错误包进链、把数据写进任意流——这些事如果每个函数都自己撸一遍字节缓冲,代码又碎又难测。Go 用 fmt 包统一了「格式化 I/O」:输出到哪里、怎么格式化两个维度正交,组合出一族函数。
核心动机是可注入 + 可测:正是因为 Fprintln(w io.Writer, ...) 的第一个参数是接口,测试才能把 bytes.Buffer 塞进去断言输出,而不是真去碰 stdout。
一句话定义
[!note] 命名边界 本文的
fmt是 Go 标准库包;代码格式化工具gofmt属于 Go 静态分析与代码检查。避免使用含混的Go fmtalias 把二者合并。
fmt 把「输出目标(stdout / 任意 Writer)」与「格式化方式(默认 / 占位符动词)」两个正交维度组合成一组函数;ln 后缀表示自动补换行,f 后缀表示用占位符动词。
核心机制:两个独立维度
不要背六个孤立函数,按两维理解:
- 写到哪
- 不带
F:写标准输出os.Stdout(如Println) - 带
F(File/Writer):写你传进去的任意io.Writer(如Fprintln(w, ...))
- 不带
- 怎么格式化
- 不带
f:默认格式;Print/Fprint/Sprint 只在相邻两个参数都不是字符串时加空格,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 的格式化形式与 printf 家族相似,但有自己的类型和动词规则;%d 表示十进制整数:
| 动词 | 含义 |
|---|---|
%d | 十进制整数 |
%s | 字符串 |
%f | 浮点(定点) |
%v | 值的默认格式,通吃 |
%T | 值的类型 |
%#v | Go 语法形式(调试神器) |
%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。
边界与易混淆点
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,可直接断言且更利于并行测试。
%v断链,比对不到原始错误;%w建链,errors.Is为 true。
延伸阅读
| 资料 | 类型 | 支撑 |
|---|---|---|
| Package fmt | 标准库 | 输出与格式化 |
| [[go-error-wrapping | 错误包装]] | 本库 |
| [[go-io-reader-writer | Reader/Writer]] | 本库 |