Go Pkg Fmt 学习笔记

go pkg fmt

Go 语言中的  fmt  包是标准库中用于 ​​ 格式化输入输出 ​​ 的核心工具,其名称源自“format”的缩写。通过 ​​ 格式化动词 ​​ 和 ​​ 灵活的函数设计 ​​,兼顾了简单使用与深度定制需求。无论是基础的终端交互,还是复杂的文件读写和字符串处理,均可通过该包高效实现。对于需要精细化控制的场景,结合自定义接口(如  StringerFormatter)能进一步提升代码的可读性和维护性

Printing

一、通用格式动词

动词说明示例(输入 → 输出)
%v默认格式输出值。对结构体,%+v 会添加字段名;%#v 输出 Go 语法表示形式。fmt.Printf("%v", 3.14)3.14
%#v输出值的 Go 语法表示(包括类型信息)。浮点数的无穷大和 NaN 显示为 ±InfNaNfmt.Printf("%#v", "hello")"hello"
%T输出值的类型(Go 语法格式)。fmt.Printf("%T", 42)int
%%输出百分号 %,不消耗任何参数。fmt.Printf("100%%")100%

二、布尔类型

动词说明示例
%t输出 truefalsefmt.Printf("%t", true)true

三、整数类型

动词说明示例(输入 → 输出)
%b二进制格式。fmt.Printf("%b", 5)101
%cUnicode 码点对应的字符。fmt.Printf("%c", 65)A
%d十进制格式。fmt.Printf("%d", 0x1F)31
%o八进制格式。%#O 添加 0o 前缀。fmt.Printf("%O", 15)0o17
%x十六进制(小写字母 a-f)。%#x 添加 0x 前缀。fmt.Printf("%x", 255)ff
%X十六进制(大写字母 A-F)。%#X 添加 0X 前缀。fmt.Printf("%X", 255)FF
%q单引号包裹的字符(支持转义不可打印字符)。fmt.Printf("%q", 'A')'A'
%UUnicode 格式:U+1234,等价于 U+%04X%#U 添加字符和引号。fmt.Printf("%U", 'A')U+0041

四、浮点数与复数

动词说明示例(输入 → 输出)
%b科学计数法(指数为 2 的幂),如 -123456p-78fmt.Printf("%b", 3.14)707065141497117p-48
%e科学计数法(小写 e)。fmt.Printf("%e", 1234.5678)1.234568e+03
%E科学计数法(大写 E)。fmt.Printf("%E", 1234.5678)1.234568E+03
%f十进制无指数格式。fmt.Printf("%f", 3.14)3.140000
%g自动选择 %e%f(根据指数大小)。精度表示最大有效数字。fmt.Printf("%.3g", 12.345)12.3
%G类似 %g,但使用大写 E。fmt.Printf("%G", 1234.5678)1.234568E+03
%x十六进制浮点数(小写)。fmt.Printf("%x", 3.14)0x1.91eb851eb851fp+1
%X十六进制浮点数(大写)。fmt.Printf("%X", 3.14)0X1.91EB851EB851FP+1

五、字符串与字节切片

动词说明示例(输入 → 输出)
%s原始字节输出(不转义)。fmt.Printf("%s", "A\tB")A B
%q双引号包裹的字符串(支持转义特殊字符)。%+q 强制 ASCII 输出。fmt.Printf("%q", "A\tB")"A\tB"
%x十六进制(小写,每字节两个字符)。% x 用空格分隔字节。fmt.Printf("%x", "Go")476f
%X十六进制(大写,每字节两个字符)。% X 用空格分隔字节。fmt.Printf("%X", "Go")476F

六、指针与切片

动词说明示例(输入 → 输出)
%p指针地址(十六进制,带 0x 前缀)。%#p 省略 0xa := 42; fmt.Printf("%p", &a)0xc0000140a8
切片默认 %v 输出 [elem1 elem2 ...]%+v 无特殊,%#v 带类型信息。fmt.Printf("%v", []int{1,2})[1 2]

七、宽度与精度控制

语法

1
%[flags][width][.precision]verb

规则

  1. 宽度:最小输出字符数(不足补空格/0)。
  2. 精度
    • 浮点数:小数点后位数(%g/%G 为最大有效位数)。
    • 字符串:最大字符数(按 rune 截断,十六进制按字节截断)。
  3. 标志
    • +:显示数值的正负号(如 %+d+42)。
    • -:左对齐(默认右对齐)。
    • 0:用 0 填充(如 %05d00042)。
    • #:改变格式(如 %#x0x1a)。
    • (空格):正数留空(如 % d42)。

示例

1
2
3
fmt.Printf("%6.2f", 3.1415)   // 输出 "  3.14"(宽度6,精度2)
fmt.Printf("%-8s", "Go")      // 输出 "Go      "(左对齐)
fmt.Printf("%+05d", 42)       // 输出 "+0042"(0填充,显示符号)

八、特殊类型处理

结构体

  • %v 格式:{field1 value1 field2 value2 ...}
  • %+v 格式:添加字段名(如 {Name:"Alice", Age:30})。
  • %#v 格式:Go 语法表示(如 main.Person{Name:"Alice", Age:30})。

Map

  • 默认格式:map[key1:value1 key2:value2 ...]
  • %#v 格式:map[KeyType]ValueType{key1:value1, ...}

接口值

  • 输出内部具体值,而非接口本身(如 var i interface{} = 4242)。

九、优先级与递归处理

  1. 接口处理优先级
    • 若值实现 Formatter 接口,调用其 Format 方法。
    • 若使用 %#v 且实现 GoStringer 接口,调用 GoString()
    • 若实现 errorString() string 接口,调用对应方法。
  2. 递归陷阱

十、Print 系列函数对比

函数行为
Print无格式,等价于对每个参数用 %v
Println自动添加空格分隔符和换行符。
Printf按指定格式输出。
Sprint返回格式化后的字符串,不输出到控制台。

十一、注意事项

  1. 未导出字段:结构体的未导出字段不会触发 String()Error() 方法。
  2. 浮点数精度%g 会删除末尾的零(如 3.1400003.14)。
  3. 切片与字符串:字节切片([]byte)使用 %s/%q 时按字符串处理。
  4. 指针与整数%d%x 等动词可格式化指针地址为整数。

Explicit argument indexes

一、基本语法

PrintfSprintfFprintf 等函数中,默认情况下格式化动词按参数顺序依次处理。但通过 [n] 语法,可以显式指定参数的索引(从 1 开始计数)。

格式规则

1
%[索引][标志][宽度][.精度]动词
  • 索引:必须是 [n] 形式,n 为参数位置(从 1 开始)。
  • 若对 宽度精度 使用 *,可用 [n] 指定参数来源。

二、核心用法

1. 交换参数顺序

1
fmt.Sprintf("%[2]d %[1]d", 11, 22) // 输出 "22 11"
  • %[2]d:使用第 2 个参数 22
  • %[1]d:使用第 1 个参数 11

2. 动态设置宽度/精度

1
2
3
fmt.Sprintf("%[3]*.[2]*[1]f", 12.0, 2, 6)
// 等价于 fmt.Sprintf("%6.2f", 12.0)
// 输出 " 12.00"
  • %[3]*:宽度取第 3 个参数 6
  • .[2]*:精度取第 2 个参数 2
  • [1]f:值取第 1 个参数 12.0

3. 重复使用参数

1
2
fmt.Sprintf("%d %d %#[1]x %#x", 16, 17)
// 输出 "16 17 0x10 0x11"
  • 前两个 %d 按顺序使用参数 1617
  • %#[1]x:再次使用第 1 个参数 16,格式化为十六进制并添加 0x
  • %#x:继续使用下一个参数(即第 2 个参数 17),格式化为十六进制。

三、索引对后续动词的影响

显式索引会重置后续动词的参数位置

1
2
fmt.Printf("%[2]s %s %[1]s", "A", "B", "C")
// 输出 "B C A"
  • %[2]s → 参数 2(“B”)。
  • 下一个 %s 使用参数 3(“C”)。
  • %[1]s → 参数 1(“A”)。

四、复杂示例

动态格式控制

1
2
3
4
5
6
7
width := 8
precision := 3
value := 3.1415926

fmt.Printf("%[3]*.[2]*[1]f", value, precision, width)
// 等价于 fmt.Printf("%8.3f", value)
// 输出 "   3.142"
  • %[3]*:宽度来自第 3 个参数 width=8
  • .[2]*:精度来自第 2 个参数 precision=3
  • [1]f:值来自第 1 个参数 value=3.1415926

多次复用同一参数

1
2
3
name := "Alice"
fmt.Printf("%[1]s: %[1]q (length=%d)", name, len(name))
// 输出 "Alice: "Alice" (length=5)"
  • %[1]s%[1]q 均使用第 1 个参数 name
  • %d 使用第 2 个参数 len(name)

五、注意事项

  1. 索引范围:索引从 1 开始,且不能超过参数总数。
  2. 动态参数顺序:显式索引后,后续动词默认从 n+1 开始。
  3. 可读性:复杂索引可能降低代码可读性,建议适度使用。
  4. 错误示例
    1
    2
    
    fmt.Printf("%[0]d", 42) // 错误:索引从 1 开始
    fmt.Printf("%[3]d", 1, 2) // 错误:索引 3 超出参数范围
    

六、使用场景

场景示例说明
交换输出顺序%[2]d %[1]d灵活控制参数顺序
重复使用参数%d %#[1]x同一值不同格式输出
动态设置格式参数%[3]*.[2]*[1]f宽度/精度来自变量
国际化输出"%[2]s: %[1]d"(不同语言顺序不同)适应不同语言的参数顺序需求

通过显式参数索引,可以实现更灵活的格式化控制,尤其在需要重复使用参数或动态设置格式时非常有用。

Format errors

一、错误类型与格式

当格式化动词与参数类型不匹配或参数数量不匹配时,fmt 包会生成特定错误信息,格式为 %!动词(错误描述)


1. 类型不匹配或未知动词

场景示例代码输出结果说明
参数类型与动词不兼容Printf("%d", "hi")%!d(string=hi)%d 需要整数,但传入字符串。
使用不存在的动词Printf("%k", 42)%!k(int=42)%k 是未定义的格式化动词。

2. 参数数量问题

场景示例代码输出结果说明
参数过多Printf("hi", "guys")hi%!(EXTRA string=guys)格式字符串无占位符,多余参数标记为 EXTRA
参数不足Printf("hi%d")hi%!d(MISSING)%d 无对应参数。

3. 动态宽度/精度参数错误

场景示例代码输出结果说明
动态宽度参数非整数Printf("%*s", 4.5, "hi")%!(BADWIDTH)hi* 需要整数,但传入浮点数 4.5
动态精度参数非整数Printf("%.*s", 4.5, "hi")%!(BADPREC)hi.* 需要整数,但传入浮点数 4.5

4. 参数索引错误

场景示例代码输出结果说明
无效的索引引用Printf("%*[2]d", 7)%!d(BADINDEX)索引 [2] 超出参数范围(仅 1 个参数)。
索引语法错误Printf("%.[2]d", 7)%!d(BADINDEX). 后缺少 * 或数值。

二、Panic 处理

如果 Error()String()GoString() 方法触发 panic,错误信息会被捕获并标记为 PANIC

示例

1
2
3
4
5
6
7
type MyType struct{}
func (m *MyType) String() string { panic("bad") }

func main() {
    var m MyType
    fmt.Printf("%s", m) // 输出:%!s(PANIC=bad)
}

特殊场景

场景输出结果说明
nil 接收者触发 panic<nil>直接输出 <nil>,无错误修饰。

三、错误格式总结

所有错误信息遵循以下模式:

1
%!动词(错误类型=描述)
  • 动词:触发错误的格式化动词(如 %d 对应 d)。
  • 错误类型:错误类别(如 BADWIDTHMISSING)。
  • 描述:具体错误原因(如 string=hi 表示传入字符串)。

四、调试建议

  1. 检查动词与参数类型:确保 %d 对应整数,%s 对应字符串等。
  2. 验证参数数量:格式化字符串中的动词数量需与参数数量匹配。
  3. 动态参数校验:确保 * 宽度/精度参数为整数。
  4. 处理 nil:为可能为 nil 的类型实现安全的 String() 方法。

五、错误示例解析

示例 1

1
fmt.Printf("%d %s", "hello", 42)
  • 输出%!d(string=hello) %!s(int=42)
  • 原因%d 需要整数但传入字符串,%s 需要字符串但传入整数。

示例 2

1
fmt.Printf("%[3]d", 1, 2)
  • 输出%!d(BADINDEX)
  • 原因:索引 [3] 超出参数范围(只有 2 个参数)。

通过理解这些错误信息,可以快速定位格式化代码中的问题,尤其是在处理复杂格式字符串或动态参数时。

Scanning

一、扫描函数分类

函数组输入源行为
Scanos.Stdin将换行符视为空格,持续读取直到所有参数匹配。
Scanlnos.Stdin遇到换行符停止扫描,参数必须后跟换行符或 EOF。
Scanfos.Stdin按格式化字符串解析输入(类似 Printf)。
Fscanio.Reader类似 Scan,但可指定任意 io.Reader(如文件、网络连接)。
Fscanlnio.Reader类似 Scanln,但可指定 io.Reader
Fscanfio.Reader类似 Scanf,但可指定 io.Reader
Sscan字符串从字符串中读取数据,行为类似 Scan
Sscanln字符串从字符串中读取数据,行为类似 Scanln
Sscanf字符串从字符串中按格式化字符串解析数据,行为类似 Scanf

二、输入处理规则

1. 空格与换行符

  • Scan/Fscan/Sscan:将换行符(\n)视为普通空格。
  • Scanln/Fscanln/Sscanln:遇到换行符后停止扫描,且要求参数后必须紧跟换行符或 EOF。
  • Scanf/Fscanf/Sscanf:根据格式字符串处理空格和换行符。

2. 格式字符串中的空格

  • 普通字符:必须与输入严格匹配(如 "a%b" 要求输入中有 a 后跟 % 匹配的内容)。
  • 换行符:格式中的 \n 匹配输入中的零或多个空格后跟一个换行符。
  • 连续空格:格式中的连续空格匹配输入中的任意多个空格(至少一个,除非在换行符旁)。

三、格式化动词

通用动词

动词说明示例输入 → 解析结果
%v解析默认格式的值(支持类型推断)。"123"int(123)
%t解析布尔值(true/false)。"true"bool(true)
%d解析十进制整数。支持 0b(二进制)、0o(八进制)、0x(十六进制)。"0x1F"int(31)
%x解析十六进制整数(不区分大小写)。"1a"int(26)
%s解析字符串(遇到空格或换行停止)。"hello world""hello"
%c解析单个 Unicode 字符(不跳过空格)。"A"rune('A')
%f解析浮点数(支持科学计数法、十六进制及下划线分隔)。"3.14_15"float64(3.1415)

浮点数与复数

  • 支持动词:%b%e%E%f、%F%g%G%x%X%v`。
  • 示例:
    1
    2
    
    var f float64
    fmt.Sscanf("0x1.8p+2", "%v", &f) // f = 6.0
    

特殊说明

  • 不支持的 Printf 特性:%p%T#+ 标志。
  • 隐式空格分隔:除 %c 外,所有动词解析前会跳过前导空格。

四、宽度控制

语法

1
%[宽度]动词
  • 宽度:指定最大读取字符数(按 rune 计数,非字节)。
  • 示例
    1
    2
    
    var s string
    fmt.Sscanf("1234567", "%5s", &s) // s = "12345"
    

五、前缀与下划线

  • 整数:支持 0b0o0x 前缀及数字下划线分隔。
    1
    2
    
    var i int
    fmt.Sscanf("0b1010_1100", "%v", &i) // i = 172
    
  • 浮点数:支持十六进制(如 0x1.2p3)和下划线分隔。
    1
    2
    
    var f float64
    fmt.Sscanf("3_141.592_6535", "%f", &f) // f = 3141.5926535
    

六、Scanner 接口

若类型实现 Scan 方法(即实现 Scanner 接口),扫描函数将调用该方法自定义解析逻辑。

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
type MyType struct{ Value int }

func (m *MyType) Scan(f fmt.ScanState, verb rune) error {
    s, _ := f.Token(true, nil) // 读取整个 token
    m.Value, _ = strconv.Atoi(string(s))
    return nil
}

func main() {
    var m MyType
    fmt.Sscanf("42", "%v", &m) // m.Value = 42
}

七、注意事项

  1. 必须传递指针

    1
    2
    3
    
    var i int
    fmt.Scan(&i) // 正确
    fmt.Scan(i)  // 错误:非指针
    
  2. 输入未完全消费

    1
    2
    
    var a, b int
    fmt.Sscanf("10 20 30", "%d %d", &a, &b) // a=10, b=20,剩余 "30" 被忽略
    
  3. 换行符处理

    • \r\n 会被统一视为 \n
  4. 潜在数据丢失

    • io.Reader 未实现 ReadRuneUnreadRune,连续扫描可能跳过字符。建议使用 bufio.NewReader 包装。

八、错误示例

示例 1:参数不足

1
2
3
var i int
_, err := fmt.Sscanf("hello", "%d", &i)
fmt.Println(err) // 输出: expected integer

示例 2:类型不匹配

1
2
3
var f float64
_, err := fmt.Sscanf("not_a_float", "%f", &f)
fmt.Println(err) // 输出: parsing "not_a_float": invalid syntax

九、高级用法

批量扫描

1
2
3
4
5
6
input := "John 30 175.5"
var name string
var age int
var height float64
fmt.Sscanf(input, "%s %d %f", &name, &age, &height)
// name="John", age=30, height=175.5

动态宽度扫描

1
2
var s1, s2 string
fmt.Sscanf("ABCDEFG", "%3s%2s", &s1, &s2) // s1="ABC", s2="DE"

Functions

总结

格式化输出函数

标准输出函数

函数名签名说明
Printfunc Print(a ...any) (n int, err error)将参数格式化并写入标准输出,不添加空格,返回写入的字节数和错误
Printffunc Printf(format string, a ...any) (n int, err error)根据格式字符串格式化参数并写入标准输出,返回写入的字节数和错误
Printlnfunc Println(a ...any) (n int, err error)将参数格式化并写入标准输出,参数间添加空格,末尾添加换行符

字符串构建函数

函数名签名说明
Sprintfunc Sprint(a ...any) string将参数格式化为字符串并返回,不添加空格
Sprintffunc Sprintf(format string, a ...any) string根据格式字符串格式化参数并返回字符串
Sprintlnfunc Sprintln(a ...any) string将参数格式化为字符串并返回,参数间添加空格,末尾添加换行符

文件/Writer 输出函数

函数名签名说明
Fprintfunc Fprint(w io.Writer, a ...any) (n int, err error)将参数格式化并写入指定的 io.Writer,不添加空格
Fprintffunc Fprintf(w io.Writer, format string, a ...any) (n int, err error)根据格式字符串格式化参数并写入指定的 io.Writer
Fprintlnfunc Fprintln(w io.Writer, a ...any) (n int, err error)将参数格式化并写入指定的 io.Writer,参数间添加空格,末尾添加换行符

字节追加函数

函数名签名说明
Appendfunc Append(b []byte, a ...any) []byte将格式化后的参数追加到字节切片中并返回
Appendffunc Appendf(b []byte, format string, a ...any) []byte根据格式字符串将格式化后的参数追加到字节切片中并返回
Appendlnfunc Appendln(b []byte, a ...any) []byte将格式化后的参数追加到字节切片中,参数间添加空格,末尾添加换行符

输入扫描函数

标准输入函数

函数名签名说明
Scanfunc Scan(a ...any) (n int, err error)从标准输入扫描文本,赋值给参数指针
Scanffunc Scanf(format string, a ...any) (n int, err error)根据格式字符串从标准输入扫描文本,赋值给参数指针
Scanlnfunc Scanln(a ...any) (n int, err error)从标准输入扫描文本直到换行,赋值给参数指针

字符串扫描函数

函数名签名说明
Sscanfunc Sscan(str string, a ...any) (n int, err error)从字符串扫描文本,赋值给参数指针
Sscanffunc Sscanf(str string, format string, a ...any) (n int, err error)根据格式字符串从字符串扫描文本,赋值给参数指针
Sscanlnfunc Sscanln(str string, a ...any) (n int, err error)从字符串扫描文本直到换行,赋值给参数指针

文件/Reader 扫描函数

函数名签名说明
Fscanfunc Fscan(r io.Reader, a ...any) (n int, err error)从指定的 io.Reader 扫描文本,赋值给参数指针
Fscanffunc Fscanf(r io.Reader, format string, a ...any) (n int, err error)根据格式字符串从指定的 io.Reader 扫描文本,赋值给参数指针
Fscanlnfunc Fscanln(r io.Reader, a ...any) (n int, err error)从指定的 io.Reader 扫描文本直到换行,赋值给参数指针

工具函数

函数名签名说明
Errorffunc Errorf(format string, a ...any) error根据格式字符串创建格式化的错误信息
FormatStringfunc FormatString(state State, verb rune) string返回格式化状态的字符串表示

接口和类型

接口名说明
Formatter自定义格式化的接口,实现该接口的类型可以自定义 fmt 包的格式化行为
GoStringer定义了 GoString() 方法的接口,用于产生适合 Go 语法表示的字符串
ScanState提供扫描功能的接口,用于自定义 Scanner 的实现
Scanner自定义扫描的接口,实现该接口的类型可以自定义 fmt 包的扫描行为
State提供格式化状态的接口,用于自定义 Formatter 的实现
Stringer定义了 String() 方法的接口,用于将对象转换为字符串表示

这些函数和接口构成了 Go 语言中用于格式化输入输出的强大工具集,为程序提供了灵活的文本处理能力。

demo 展示

实现 Stringer 和 GoStringer

img1
1
2
persion: Name: John, Age: 30
persion: Persion{Name: "John", Age: 30}

实现 Formatter

img2
1
2
3
4
5
默认: Host: localhost, Port: 5432, User: user, DB: example_db
详细信息: DBConfig{Host: localhost, Port: 5432, Username: user, Password: password, Database: example_db}
字符串表示: localhost:5432/example_db
带引号: "localhost:5432/example_db"
其他格式: unsupported format: x

Go fmt 包中的三个格式化接口及其优先级

在 Go 的 fmt 包中,FormatterStringerGoStringer 这三个接口用于控制值的格式化输出。它们各有特定用途,并按照一定的优先级顺序被调用。

优先级顺序(从高到低)

  1. Formatter 接口 (最高优先级)
  2. GoStringer 接口 (中间优先级,特定于 %#v 格式)
  3. Stringer 接口 (最低优先级)

1. Formatter 接口

1
2
3
type Formatter interface {
    Format(f State, verb rune)
}
  • 最高优先级:如果一个类型实现了此接口,fmt 包将优先使用它来处理所有格式化操作
  • 完全控制:可以自定义处理所有格式动词和标志
  • 灵活性:能够根据不同的格式动词(v, s, q等)和标志(+, #等)提供不同输出
  • 应用场景:需要对不同格式动词提供完全不同的输出形式时

2. GoStringer 接口

1
2
3
type GoStringer interface {
    GoString() string
}
  • 特定优先级:仅当使用 %#v 格式动词且类型未实现 Formatter 接口时生效
  • 用途:提供符合 Go 语法的值表示,主要用于调试和开发
  • 典型输出:类似于可用于重建对象的 Go 代码片段
  • 应用场景:调试时想查看值的 Go 语法表示

3. Stringer 接口

1
2
3
type Stringer interface {
    String() string
}
  • 基础优先级:当类型未实现 Formatter 接口(对于 %v%s 等),且未使用 %#v(或实现了但未使用 GoStringer)时生效
  • 用途:提供类型的"原生"字符串表示
  • 简单直接:只需返回一个字符串,不需要处理不同的格式动词
  • 应用场景:希望类型有一个默认的字符串表示时

调用顺序逻辑

当调用 fmt 包的函数(如 Printf, Sprintf 等)时:

  1. 首先检查值是否实现了 Formatter 接口,如果是,则调用其 Format 方法
  2. 如果没有实现 Formatter,但格式是 %#v 且实现了 GoStringer,则调用 GoString()
  3. 如果前两者都不适用,但实现了 Stringer,则在适当的格式中使用 String()
  4. 如果都没实现,则使用 fmt 包的默认格式化规则
Licensed under CC BY-NC-SA 4.0
comments powered by Disqus