在 Go 语言中,fmt包用于实现格式化的输入输出,其功能类似于 C 语言中的printfscanf。格式化动词(format verbs)提供了多种方式来控制数据的输出格式。本文将详细讲解 Go 语言中常用的格式化动词及其用法。

go fmt.Printf

Go 语言的格式化动词大致分为以下几类:

一般动词

  • %v:以默认格式打印值。对于结构体,%+v 会打印字段名。
  • %#v:以 Go 语法表示值。
  • %T:以 Go 语法表示值的类型。
  • %%:打印一个百分号字符。

示例代码:

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func main() {
	p := Person{"Alice", 30}

	fmt.Printf("%%v 默认格式:%v\n", p)
	fmt.Printf("%%+v 包含字段名:%+v\n", p)
	fmt.Printf("%%#v Go 语法表示:%#v\n", p)
	fmt.Printf("%%T 类型表示:%T\n", p)
	fmt.Printf("%%%% 百分号:%%\n")
}

输出:

%v 默认格式:{Alice 30}
%+v 包含字段名:{Name:Alice Age:30}
%#v Go 语法表示:main.Person{Name:"Alice", Age:30}
%T 类型表示:main.Person
%% 百分号:%

布尔类型

  • %t:打印布尔值 truefalse

示例代码:

package main

import "fmt"

func main() {
	fmt.Printf("%%t 布尔值:%t\n", true)
}

输出:

%t 布尔值:true

在当前版本的 Go 中,%v%t 对布尔值的输出是相同的,但使用 %t 可以明确地表明你正在格式化布尔值。这样,代码的可读性更高,并且格式化意图更加明确。如果将来有新的格式化需求或规则,使用 %t 可能更容易维护和理解。

整数类型

  • %b:二进制表示。
  • %c:对应的 Unicode 字符。
  • %d:十进制表示。
  • %o:八进制表示。
  • %O:带 0o 前缀的八进制表示。
  • %q:单引号字符字面量,使用 Go 语法进行安全转义。
  • %x:十六进制表示,字母 a-f 使用小写。
  • %X:十六进制表示,字母 A-F 使用大写。
  • %U:Unicode 格式,类似于 U+1234

示例代码:

package main

import "fmt"

func main() {
	num := 255
	fmt.Printf("%%b 二进制:%b\n", num)
	fmt.Printf("%%c 字符:%c\n", num)
	fmt.Printf("%%d 十进制:%d\n", num)
	fmt.Printf("%%o 八进制:%o\n", num)
	fmt.Printf("%%O 带前缀的八进制:%O\n", num)
	fmt.Printf("%%q 单引号字符:%q\n", num)
	fmt.Printf("%%x 小写十六进制:%x\n", num)
	fmt.Printf("%%X 大写十六进制:%X\n", num)
	fmt.Printf("%%U Unicode 格式:%U\n", num)
}

输出:

%b 二进制:11111111
%c 字符:ÿ
%d 十进制:255
%o 八进制:377
%O 带前缀的八进制:0o377
%q 单引号字符:'ÿ'
%x 小写十六进制:ff
%X 大写十六进制:FF
%U Unicode 格式:U+00FF

浮点数和复数

  • %b:无小数点的科学计数法,以 2 的幂为指数,例如 -123456p-78
  • %e:科学计数法,使用小写的 e,例如 -1.234456e+78
  • %E:科学计数法,使用大写的 E,例如 -1.234456E+78
  • %f:十进制形式,无科学计数法,例如 123.456
  • %F:与 %f 相同,表示十进制形式。
  • %g:自动选择 %e%f,对于较大指数使用 %e,对于其他情况使用 %f。精度会影响显示的有效数字数量。
  • %G:自动选择 %E%F,与 %g 类似,但使用大写的 E%F
  • %x:十六进制表示,使用小数点的科学计数法,例如 -0x1.23abcp+20
  • %X:十六进制表示,使用大写字母,类似 -0X1.23ABCP+20

示例代码:

package main

import "fmt"

func main() {
	num := 12345.6789
	complexNum := 1.234 + 5.678i

	fmt.Printf("%%b 无小数点科学计数法:%b\n", num)
	fmt.Printf("%%e 科学计数法(小写 e):%e\n", num)
	fmt.Printf("%%E 科学计数法(大写 E):%E\n", num)
	fmt.Printf("%%f 十进制形式:%f\n", num)
	fmt.Printf("%%F 与 %%f 相同:%F\n", num)
	fmt.Printf("%%g 自动选择:%g\n", num)
	fmt.Printf("%%G 自动选择(大写):%G\n", num)
	fmt.Printf("%%x 十六进制表示:%x\n", num)
	fmt.Printf("%%X 十六进制表示(大写):%X\n", num)
	fmt.Println("----------")
	fmt.Printf("%%b 无小数点科学计数法(复数):%b\n", complexNum)
	fmt.Printf("%%e 科学计数法(小写 e)(复数):%e\n", complexNum)
	fmt.Printf("%%E 科学计数法(大写 E)(复数):%E\n", complexNum)
	fmt.Printf("%%f 十进制形式(复数):%f\n", complexNum)
	fmt.Printf("%%F 与 %%f 相同(复数):%F\n", complexNum)
	fmt.Printf("%%g 自动选择(复数):%g\n", complexNum)
	fmt.Printf("%%G 自动选择(大写)(复数):%G\n", complexNum)
	fmt.Printf("%%x 十六进制表示(复数):%x\n", complexNum)
	fmt.Printf("%%X 十六进制表示(大写)(复数):%X\n", complexNum)
}

输出:

%b 无小数点科学计数法:6787108751669409p-39
%e 科学计数法(小写 e):1.234568e+04
%E 科学计数法(大写 E):1.234568E+04
%f 十进制形式:12345.678900
%F 与 %f 相同:12345.678900
%g 自动选择:12345.6789
%G 自动选择(大写):12345.6789
%x 十六进制表示:0x1.81cd6e631f8a1p+13
%X 十六进制表示(大写):0X1.81CD6E631F8A1P+13
----------
%b 无小数点科学计数法(复数):(5557441940175192p-52+6392859671052419p-50i)
%e 科学计数法(小写 e)(复数):(1.234000e+00+5.678000e+00i)
%E 科学计数法(大写 E)(复数):(1.234000E+00+5.678000E+00i)
%f 十进制形式(复数):(1.234000+5.678000i)
%F 与 %f 相同(复数):(1.234000+5.678000i)
%g 自动选择(复数):(1.234+5.678i)
%G 自动选择(大写)(复数):(1.234+5.678i)
%x 十六进制表示(复数):(0x1.3be76c8b43958p+00+0x1.6b645a1cac083p+02i)
%X 十六进制表示(大写)(复数):(0X1.3BE76C8B43958P+00+0X1.6B645A1CAC083P+02i)

字符串和字节切片

  • %s:原始字节。
  • %q:双引号字符串,使用 Go 语法进行安全转义。
  • %x:十六进制表示,每两个字节一个字符。
  • %X:十六进制表示,每两个字节一个字符,大写字母。

示例代码:

package main

import "fmt"

func main() {

	str := "hello"
	str1 := `"hello"`
	data := []byte{72, 101, 108, 108, 111}

	fmt.Printf("%%s 原始字节:%s\n", str)
	fmt.Printf("%%q 双引号字符串:%q\n", str)
	fmt.Printf("%%q 双引号字符串:%q\n", str1)
	fmt.Printf("%%x 小写十六进制:%x\n", data)
	fmt.Printf("%%X 大写十六进制:%X\n", data)
}

输出:

%s 原始字节:hello
%q 双引号字符串:"hello"
%q 双引号字符串:"\"hello\""
%x 小写十六进制:48656c6c6f
%X 大写十六进制:48656C6C6F

切片

对于切片,%p 用于打印第一个元素的地址,以十六进制表示,带 0x 前缀。其他格式化动词(如 %b, %d, %o, %x, %X)在切片上没有特殊意义,主要用于处理切片内容的打印。

示例代码:

package main

import "fmt"

func main() {
	slice := []int{10, 20, 30}

	fmt.Printf("%%p 切片首元素地址:%p\n", &slice[0])
	fmt.Printf("%%b 二进制表示(切片):%b\n", slice)
	fmt.Printf("%%d 十进制表示(切片):%d\n", slice)
	fmt.Printf("%%o 八进制表示(切片):%o\n", slice)
	fmt.Printf("%%x 小写十六进制表示(切片):%x\n", slice)
	fmt.Printf("%%X 大写十六进制表示(切片):%X\n", slice)
}

输出:

%p 切片首元素地址:0xc000010200
%b 二进制表示(切片):[00001010 00010100 00011110]
%d 十进制表示(切片):[10 20 30]
$o 八进制表示(切片):[12 24 36]
%x 小写十六进制表示(切片):[a 14 1e]
%X 大写十六进制表示(切片):[A 14 1E]

指针

对于指针,%p 用于打印指针地址,以十六进制表示,带 0x 前缀。%b, %d, %o, %x, %X 等动词也可以用来处理指针,但它们会将指针值作为整数处理。

示例代码:

package main

import "fmt"

func main() {
	var num int = 42
	ptr := &num

	fmt.Printf("%%p 指针地址:%p\n", ptr)
	fmt.Printf("%%b 二进制表示(指针):%b\n", ptr)
	fmt.Printf("%%d 十进制表示(指针):%d\n", ptr)
	fmt.Printf("%%o 八进制表示(指针):%o\n", ptr)
	fmt.Printf("%%x 小写十六进制表示(指针):%x\n", ptr)
	fmt.Printf("%%X 大写十六进制表示(指针):%X\n", ptr)
}

输出:

%p 指针地址:0xc000010200
%b 二进制表示(指针):1100000000000000000000100000000000000000000000000000000000000000
%d 十进制表示(指针):140737488355328
%o 八进制表示(指针):1000000000000
%x 小写十六进制表示(指针):c000010200
%X 大写十六进制表示(指针):C000010200

各类数据类型的 %v 默认格式

Go 语言中的 %v 格式化动词用于以默认格式打印值。以下是不同类型数据的默认格式化规则:

  • 布尔值 (bool): 使用 %t 打印 truefalse
  • 整数 (int, int8 等): 使用 %d 打印十进制整数。如果使用 %#v,则以十六进制形式加上 0x 前缀打印。
  • 无符号整数 (uint, uint8 等): 同样使用 %d 打印十进制整数,而使用 %#v 时,则以十六进制形式加上 0x 前缀打印。
  • 浮点数 (float32, complex64 等): 使用 %g 打印,根据需要选择科学计数法或十进制表示。
  • 字符串 (string): 使用 %s 打印原始字符串。
  • 通道 (chan): 使用 %p 打印通道的地址。
  • 指针 (pointer): 使用 %p 打印指针的地址。

对于复合对象,如结构体、数组、切片和 map,%v 会递归打印其元素,格式如下:

  • 结构体 (struct): 以 {field0 field1 ...} 的形式打印字段及其值。
  • 数组、切片 (array, slice): 以 [elem0 elem1 ...] 的形式打印元素。
  • map (map): 以 map[key1:value1 key2:value2 ...] 的形式打印键值对。
  • 指向上述类型的指针 (pointer to above): 以 &{}, &[], &map[] 的形式打印指针。

示例代码:

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func main() {
	b := true
	i := 123
	u := uint(456)
	f := 789.01
	s := "hello"
	c := make(chan int)
	p := &Person{"Alice", 30}

	// Default format for each type
	fmt.Printf("%%v 布尔值:%v\n", b)
	fmt.Printf("%%v 整数:%v\n", i)
	fmt.Printf("%%#v 十六进制整数:%#v\n", i)
	fmt.Printf("%%v 无符号整数:%v\n", u)
	fmt.Printf("%%#v 十六进制无符号整数:%#v\n", u)
	fmt.Printf("%%v 浮点数:%v\n", f)
	fmt.Printf("%%v 字符串:%v\n", s)
	fmt.Printf("%%v 通道:%v\n", c)
	fmt.Printf("%%v 指针:%v\n", p)

	// Compound objects
	arr := [3]int{1, 2, 3}
	slice := []string{"a", "b", "c"}
	m := map[string]int{"one": 1, "two": 2}

	fmt.Printf("%%v 数组:%v\n", arr)
	fmt.Printf("%%v 切片:%v\n", slice)
	fmt.Printf("%%v map:%v\n", m)
	fmt.Printf("%%v 数组指针:%v\n", &arr)
	fmt.Printf("%%v 切片指针:%v\n", &slice)
	fmt.Printf("%%v map指针:%v\n", &m)
}

输出:

%v 布尔值:true
%v 整数:123
%#v 十六进制整数:123
%v 无符号整数:456
%#v 十六进制无符号整数:0x1c8
%v 浮点数:789.01
%v 字符串:hello
%v 通道:0xc00007c070
%v 指针:&{Alice 30}
%v 数组:[1 2 3]
%v 切片:[a b c]
%v map:map[one:1 two:2]
%v 数组指针:&[1 2 3]
%v 切片指针:&[a b c]
%v map指针:&map[one:1 two:2]

指定宽度与精度

在 Go 语言中,格式化动词的宽度和精度设置用于控制输出的格式和显示。

宽度设置

宽度指的是格式化输出的最小宽度。它确保输出至少占据指定的字符位置。如果实际内容小于指定的宽度,格式化函数会在内容的左侧(默认)填充空格,直到达到指定的宽度。如果实际内容的宽度超过了指定的宽度,fmt 包的格式化动词会忽略宽度设置,直接输出实际内容的完整形式。对于数字和字符串,这个设置影响输出的对齐方式。

  • 设置宽度:在格式化动词前面加上一个数字,如 %10f。这表示输出的总宽度至少为 10 个字符。

效果示例:

package main

import "fmt"

func main() {
	num := 123.45
	str := "GoLang"

	fmt.Printf("宽度 10 的浮点数:%10f\n", num) // 输出 123.450000 右对齐,宽度为 10
	fmt.Printf("宽度 10 的字符串:%10s\n", str) // 输出 '      GoLang' 右对齐,宽度为 10
}

输出:

宽度 10 的浮点数:  123.450000
宽度 10 的字符串:    GoLang

精度设置

精度指的是显示的小数点后的位数(对于浮点数)或字符数(对于字符串)。它控制了输出的详细程度。例如,对于浮点数,精度设置决定了显示多少位小数;对于字符串,精度设置限制了显示的最大字符数。

  • 设置精度:在格式化动词中加上一个句点和数字,如 %.2f。这表示浮点数的精度为 2,即小数点后显示 2 位数字。

效果示例:

package main

import "fmt"

func main() {
	num := 123.456789
	str := "GoLang Programming"

	fmt.Printf("默认精度的浮点数:%f\n", num)   // 输出 123.456789 默认精度 6
	fmt.Printf("精度 2 的浮点数:%.2f\n", num) // 输出 123.46 精度为 2
	fmt.Printf("精度 5 的字符串:%.5s\n", str) // 输出 'GoLan' 精度为 5,截断字符串
}

输出:

默认精度的浮点数:123.456789
精度 2 的浮点数:123.46
精度 5 的字符串:GoLan

宽度和精度的组合

你可以同时设置宽度和精度。宽度确定输出的总宽度,而精度确定内容的详细程度。例如 %9.2f 表示宽度为 9,总宽度包含小数点及两位小数。

示例代码:

package main

import "fmt"

func main() {
	num := 123.456
	str := "GoLang"

	fmt.Printf("宽度 9,精度 2 的浮点数:%9.2f\n", num) // 输出  123.46 宽度 9,精度 2
	fmt.Printf("宽度 10,精度 5 的字符串:%10.5s\n", str) // 输出 '     GoLan' 宽度 10,精度 5
}

输出:

宽度 9,精度 2 的浮点数:  123.46
宽度 10,精度 5 的字符串:     GoLan

其他标志

在 Go 语言的 fmt 包中,除了基本的格式化动词外,还有一些其他的标志可以进一步控制输出的格式。这些标志包括 +-# (空格)、0 等,以及 * 符号。

+:始终打印符号

  • 对于数字类型,总是打印正负号。
  • 对于 %q,保证输出 ASCII 字符串。

示例代码:

package main

import "fmt"

func main() {
	num := 42
	str := "hello 世界 🌍"

	fmt.Printf("%%+d 始终打印符号:%+d\n", num)
	fmt.Printf("%%+q 保证 ASCII 输出:%+q\n", str)
}

输出:

%+d 始终打印符号:+42
%+q 保证 ASCII 输出:"hello \u4e16\u754c \U0001f30d"

-:左对齐字段

将字段内容左对齐,填充空间在右侧。

示例代码:

package main

import "fmt"

func main() {
	num := 42
	str := "hello"

	fmt.Printf("%%6d 右对齐,宽度 6:%6d\n", num)
	fmt.Printf("%%-6d 左对齐,宽度 6:%-6d\n", num)
	fmt.Printf("%%10s 右对齐,宽度 10:%10s\n", str)
	fmt.Printf("%%-10s 左对齐,宽度 10:%-10s\n", str)
}

输出:

%6d 右对齐,宽度 6:    42
%-6d 左对齐,宽度 6:42
%10s 右对齐,宽度 10:     hello
%-10s 左对齐,宽度 10:hello

#:使用特殊格式

  • 为二进制、八进制和十六进制值添加前缀。
  • 对于 %q,如果 [strconv.CanBackquote] 返回 true,打印原始(反引号)字符串。
  • 对于 %e%E%f%F%g%G,始终打印小数点。
  • 对于 %g%G,不移除尾随零。
  • 对于 %U,以 U+0078 'x' 形式写出可打印字符。

示例代码:

package main

import "fmt"

func main() {
	num := 42
	str := "hello"

	fmt.Printf("%%#b 二进制,前缀:%#b\n", num)
	fmt.Printf("%%#o 八进制,前缀:%#o\n", num)
	fmt.Printf("%%#x 十六进制,前缀:%#x\n", num)
	fmt.Printf("%%#X 大写十六进制,前缀:%#X\n", num)
	fmt.Printf("%%#q 原始字符串:%#q\n", str)
	fmt.Printf("%%#e 科学计数法,带小数点:%#e\n", 12345.6789)
	fmt.Printf("%%#g 不移除尾随零:%#g\n", 12345.6789)
	fmt.Printf("%%#U Unicode 格式:%#U\n", 'x')
}

输出:

%#b 二进制,前缀:0b101010
%#o 八进制,前缀:052
%#x 十六进制,前缀:0x2a
%#X 大写十六进制,前缀:0X2A
%#q 原始字符串:`hello`
%#e 科学计数法,带小数点:1.234568e+04
%#g 不移除尾随零:12345.6789
%#U Unicode 格式:U+0078 'x'

(空格):留空空间

  • 为数字之间留空格以避免省略符号。
  • 在打印字节切片的十六进制表示时,添加空格分隔每两个字节。

示例代码:

package main

import "fmt"

func main() {
	num1 := 42
	num2 := -42
	data := []byte{72, 101, 108, 108, 111}

	fmt.Printf("%% d 留空空间:% d\n", num1)
	fmt.Printf("%% d 留空空间:% d\n", num2)
	fmt.Printf("%%x 十六进制:%x\n", data)
	fmt.Printf("%% x 十六进制,分隔字节:% x\n", data)
}

输出:

% d 留空空间: 42
% d 留空空间:-42
%x 十六进制:48656c6c6f
% x 十六进制,分隔字节:48 65 6c 6c 6f

0:用零填充

用零填充数字,移动填充在符号后面。

示例代码:

package main

import "fmt"

func main() {
	num := 42
	num1 := -42

	fmt.Printf("%%05d 用零填充,宽度 5:%05d\n", num)
	fmt.Printf("%%05d 用零填充,宽度 5:%05d\n", num1)
	fmt.Printf("%%#04x 用零填充,宽度 4,带前缀:%#04x\n", num)
	fmt.Printf("%%#04x 用零填充,宽度 4,带前缀:%#04x\n", num1)
}

输出:

%05d 用零填充,宽度 5:00042
%05d 用零填充,宽度 5:-0042
%#04x 用零填充,宽度 4,带前缀:0x002a
%#04x 用零填充,宽度 4,带前缀:-0x02a

*:动态指定宽度和精度

在 Go 的 fmt 包中,一些格式化动词(如 %s, %d, %f 等)可以接受宽度和精度参数。这些参数通常是直接在格式化字符串中指定的,例如 %10s 表示宽度为 10 的字符串。如果你使用 * 符号,宽度或精度将通过额外的参数传递,而不是硬编码。

语法

  • %*s:宽度由后续参数提供。
  • %.*s:精度由后续参数提供。
  • %*.*s:宽度和精度都由后续参数提供。

示例代码

下面是一些示例代码,演示如何使用 * 符号动态设置格式化宽度和精度:

package main

import (
    "fmt"
)

func main() {
    // 使用 * 符号动态设置宽度
    width := 20
    str := "hello"
    // 输出宽度为 20 的字符串 "hello"
    fmt.Printf("|%*s|\n", width, str)

    // 使用 * 符号动态设置精度
    precision := 3
    pi := 3.14159
    // 输出精度为 3 的浮点数 3.142
    fmt.Printf("|%.*f|\n", precision, pi)

    // 使用 * 符号动态设置宽度和精度
    width = 10
    precision = 2
    // 输出宽度为 10,精度为 2 的浮点数 3.14
    fmt.Printf("|%*.*f|\n", width, precision, pi)
}

输出结果

|               hello|
|3.142|
|      3.14|

显式参数索引

在 Go 的格式化函数中(比如 fmt.Printffmt.Sprintffmt.Fprintf),你可以用 [n] 来告诉函数使用第 n 个参数来进行格式化,使用 [n] 后,后面的格式化动词会依次使用下一个参数,除非再次指定。你可以用这种方式灵活地控制输出的格式,特别是当你需要重复使用参数或者改变参数的显示方式时。

例子 1

package main

import (
	"fmt"
)

func main() {
	// 使用索引进行参数复用
	fmt.Printf("%[1]s 和 %[1]s 的朋友\n", "Tom")
	// 输出 "Tom 和 Tom 的朋友"

	fmt.Printf("%[2]d 是大于 %[1]d 的数字\n", 11, 22)
	// 输出 "22 是大于 11 的数字"

	// 展示使用 [n] 后,后面的动词自动递增
	fmt.Printf("%d, %[3]d, %[1]d, %d, %d, %[1]d, %d\n", 100, 200, 300)
	// 输出 "100, 300, 100, 200, 300, 100, 200"
}

这个示例展示了索引取值的几种用法:

  • 使用 [n] 语法来复用同一个参数。
  • 控制参数的选择和格式化方式。
  • 在格式化字符串中,使用 [n] 语法后,后续的动词会自动递增,除非你再次指定索引。

例子 2

package main

import (
	"fmt"
)

func main() {
	fmt.Printf("%[3]*.[2]*[1]f\n", 12.0, 2, 6)
    // 输出 " 12.00"
}

%[3]*.[2]*[1]f 的解释:

  • [3]* 表示宽度由第三个参数(6)决定。
  • .[2]* 表示精度由第二个参数(2)决定。
  • [1]f 表示使用第一个参数(12.0)来格式化。

所以,输出是 " 12.00",这里的宽度是 6,精度是 2。

额外的格式化规则和接口处理

在 Go 语言的 fmt 包中,除了基本的格式化动词外,还有一些额外的规则和接口处理机制,这些机制会影响最终的输出结果。以下是这些规则的详细讲解和示例:

Print、Println 和 Printf

  • fmt.Print:相当于对每个操作数使用 %v 格式化,且不会添加任何额外格式。
  • fmt.Println:在每个操作数之间插入空格,并在末尾添加换行符。
  • fmt.Printf:允许使用格式化动词对操作数进行自定义格式化。

示例代码:

package main

import "fmt"

func main() {
	i := 23

	fmt.Print("Print 默认格式:")
	fmt.Print(i)
	fmt.Print("\n")

	fmt.Println("Println 默认格式:", i)

	fmt.Printf("Printf 默认格式:%v\n", i)
}

输出:

Print 默认格式:23
Println 默认格式: 23
Printf 默认格式:23

接口处理

对于实现了特定接口的操作数,fmt 包会根据这些接口的实现来决定如何格式化:

  1. reflect.Value:如果操作数是 reflect.Value 类型,fmt 包会用其持有的具体值替换 reflect.Value 对象。

  2. Formatter 接口:如果操作数实现了 Formatter 接口(包含 Format 方法),fmt 包会调用该方法来控制格式化。

  3. GoStringer 接口:如果 %#v 格式化动词用于操作数,并且该操作数实现了 GoStringer 接口(包含 GoString 方法),fmt 包会调用该方法。

示例代码:

package main

import (
	"fmt"
	"reflect"
)

type CustomFormatter struct {
	Value int
}

func (cf CustomFormatter) Format(f fmt.State, c rune) {
	fmt.Fprintf(f, "[%d]", cf.Value)
}

type GoStringerExample struct {
	Value int
}

func (gse GoStringerExample) GoString() string {
	return fmt.Sprintf("<GoString: %d>", gse.Value)
}

func main() {
	// reflect.Value
	v := reflect.ValueOf(42)
	fmt.Printf("reflect.Value 格式:%v\n", v)

	// Formatter 接口
	cf := CustomFormatter{Value: 100}
	fmt.Printf("Formatter 接口格式:%v\n", cf)

	// GoStringer 接口
	gse := GoStringerExample{Value: 200}
	fmt.Printf("GoStringer 接口格式:%#v\n", gse)
}

输出:

reflect.Value 格式:42
Formatter 接口格式:[100]
GoStringer 接口格式:<GoString: 200>

错误和字符串接口处理

  1. error 接口:如果操作数实现了 error 接口,fmt 包会调用 Error 方法来将对象转换为字符串。

  2. String() 方法:如果操作数实现了 String() string 方法,fmt 包会调用该方法将对象转换为字符串。

示例代码:

package main

import "fmt"

type MyError struct {
	Message string
}

func (e MyError) Error() string {
	return "MyError: " + e.Message
}

type StringerExample struct {
	Value string
}

func (se StringerExample) String() string {
	return "Stringer: " + se.Value
}

func main() {
	// 实现了 error 接口
	err := MyError{Message: "something went wrong"}
	fmt.Printf("error 接口格式:%v\n", err)

	// 实现了 String() 方法
	se := StringerExample{Value: "example"}
	fmt.Printf("String() 方法格式:%v\n", se)
}

输出:

error 接口格式:MyError: something went wrong
String() 方法格式:Stringer: example

复合操作数的格式化

对于切片和结构体等复合操作数,格式化动词会递归地应用于其元素。例如,%q 会对每个字符串元素进行引用,而 %6.2f 会对每个浮点数元素进行格式化。

示例代码:

package main

import "fmt"

func main() {
	strings := []string{"hello", "world"}
	floats := []float64{12.345, 67.890}

	fmt.Printf("%%q 格式化切片:%q\n", strings)
	fmt.Printf("%%6.2f 格式化浮点数切片:%6.2f %6.2f\n", floats[0], floats[1])
}

输出:

%q 格式化切片:"hello" "world"
%6.2f 格式化浮点数切片: 12.35  67.89

避免递归

在自定义类型的 String() 方法中,为了避免递归调用,可以将值转换为基本类型后再进行格式化。

示例代码:

package main

import "fmt"

type X string

func (x X) String() string {
	return fmt.Sprintf("<%s>", string(x))
}

func main() {
	x := X("example")
	fmt.Printf("避免递归格式化:%v\n", x)
}

输出:

避免递归格式化:<example>

结语

了解这些格式化动词可以帮助你更灵活地控制 Go 程序中的输出格式。无论是打印基本数据类型还是复杂的数据结构,掌握这些动词和标志能够提高代码的可读性和调试效率。希望本文的详细讲解和示例能够帮助你更好地使用 fmt 包进行格式化输出。


也可以看看