Golang fmt.Formatter 接口
最后修改时间 2025 年 5 月 8 日
本教程解释了如何在 Go 中使用 fmt.Formatter 接口。我们将通过自定义格式的实际示例来介绍接口基础知识。
fmt.Formatter 接口允许类型控制它们的格式。它通过支持格式动词和标志,比 Stringer 接口提供了更大的灵活性。
在 Go 中,当您需要超出简单字符串转换的自定义格式行为时,会使用 fmt.Formatter。它与 Printf 和 Sprintf 等所有 fmt 打印函数一起工作。
基本的 fmt.Formatter 接口定义
fmt.Formatter 接口要求实现一个方法。此示例显示了接口定义和基本实现。
注意: Format 方法让您可以完全控制格式。
package main
import "fmt"
type Point struct {
X, Y int
}
func (p Point) Format(f fmt.State, verb rune) {
switch verb {
case 'v':
if f.Flag('+') {
fmt.Fprintf(f, "Point{X: %d, Y: %d}", p.X, p.Y)
} else {
fmt.Fprintf(f, "{%d %d}", p.X, p.Y)
}
case 's':
fmt.Fprintf(f, "(%d,%d)", p.X, p.Y)
default:
fmt.Fprintf(f, "%%!%c(Point=%d,%d)", verb, p.X, p.Y)
}
}
func main() {
p := Point{3, 4}
fmt.Printf("%v\n", p) // {3 4}
fmt.Printf("%+v\n", p) // Point{X: 3, Y: 4}
fmt.Printf("%s\n", p) // (3,4)
fmt.Printf("%d\n", p) // %!d(Point=3,4)
}
Point 类型实现了 Format 以处理不同的格式动词。它支持带 + 标志的 'v' 和带自定义格式的 's' 动词。
处理宽度和精度
fmt.State 参数提供了对格式标志的访问。此示例展示了如何处理宽度和精度规范。
package main
import (
"fmt"
"strings"
)
type Banner string
func (b Banner) Format(f fmt.State, verb rune) {
switch verb {
case 's', 'v':
width, hasWidth := f.Width()
if hasWidth {
padded := strings.Repeat(string(b), width/len(b)+1)
fmt.Fprint(f, padded[:width])
} else {
fmt.Fprint(f, string(b))
}
default:
fmt.Fprintf(f, "%%!%c(Banner=%s)", verb, b)
}
}
func main() {
b := Banner("Go")
fmt.Printf("%s\n", b) // Go
fmt.Printf("%5s\n", b) // GoGoG
fmt.Printf("%.3s\n", b) // Go
fmt.Printf("%5.3s\n", b) // Go
}
Banner 类型使用 Width() 来检查宽度规范。它在打印时重复 banner 文本以填充请求的宽度。
自定义标志处理
fmt.State 接口提供标志信息。此示例演示了格式化程序中的自定义标志处理。
package main
import "fmt"
type Temperature float64
func (t Temperature) Format(f fmt.State, verb rune) {
switch verb {
case 'f', 'F':
prec, hasPrec := f.Precision()
if !hasPrec {
prec = 1
}
if f.Flag('+') {
fmt.Fprintf(f, "%+.*f°F", prec, float64(t))
} else {
fmt.Fprintf(f, "%.*f°C", prec, float64(t)*5/9)
}
default:
fmt.Fprintf(f, "%%!%c(Temperature=%f)", verb, t)
}
}
func main() {
temp := Temperature(32.0)
fmt.Printf("%f\n", temp) // 0.0°C
fmt.Printf("%+.2f\n", temp) // +32.00°F
fmt.Printf("%5.1f\n", temp) // 0.0°C
}
Temperature 类型检查 '+' 标志以在华氏度和摄氏度之间切换。它还处理小数位的精度规范。
与 Stringer 接口结合使用
一个类型可以同时实现 fmt.Stringer 和 fmt.Formatter。此示例展示了它们如何协同工作。
package main
import "fmt"
type Color struct {
R, G, B uint8
}
func (c Color) String() string {
return fmt.Sprintf("RGB(%d,%d,%d)", c.R, c.G, c.B)
}
func (c Color) Format(f fmt.State, verb rune) {
switch verb {
case 'v':
if f.Flag('#') {
fmt.Fprintf(f, "Color{R:0x%02x, G:0x%02x, B:0x%02x}", c.R, c.G, c.B)
} else {
fmt.Fprint(f, c.String())
}
case 's':
fmt.Fprint(f, c.String())
case 'x', 'X':
fmt.Fprintf(f, "%02x%02x%02x", c.R, c.G, c.B)
default:
fmt.Fprintf(f, "%%!%c(Color=%s)", verb, c.String())
}
}
func main() {
c := Color{255, 0, 128}
fmt.Println(c) // RGB(255,0,128)
fmt.Printf("%v\n", c) // RGB(255,0,128)
fmt.Printf("%#v\n", c) // Color{R:0xff, G:0x00, B:0x80}
fmt.Printf("%x\n", c) // ff0080
}
Color 类型使用 String() 进行简单的字符串转换。Format 方法提供了额外的格式化选项,包括十六进制输出以及带 # 标志的结构体样式打印。
格式化复数
fmt.Formatter 可用于创建自定义复数格式。此示例显示了极坐标表示。
package main
import (
"fmt"
"math"
)
type Polar complex128
func (p Polar) Format(f fmt.State, verb rune) {
r := real(p)
i := imag(p)
switch verb {
case 'v', 'f', 'g':
mag := math.Hypot(r, i)
ang := math.Atan2(i, r) * 180 / math.Pi
prec, hasPrec := f.Precision()
if !hasPrec {
prec = 2
}
fmt.Fprintf(f, "%.*f∠%.*f°", prec, mag, prec, ang)
default:
fmt.Fprintf(f, "%%!%c(Polar=%f+%fi)", verb, r, i)
}
}
func main() {
p := Polar(1 + 1i)
fmt.Printf("%v\n", p) // 1.41∠45.00°
fmt.Printf("%.3f\n", p) // 1.414∠45.000°
fmt.Printf("%.1g\n", p) // 1.4∠45.0°
}
Polar 类型以极坐标表示法格式化复数。它计算幅度和角度,然后以指定的精度格式化它们。
自定义日期格式
实现 fmt.Formatter 可以实现灵活的日期格式。此示例显示了带有不同动词的自定义日期输出。
package main
import (
"fmt"
"time"
)
type MyDate time.Time
func (d MyDate) Format(f fmt.State, verb rune) {
t := time.Time(d)
switch verb {
case 'd':
fmt.Fprintf(f, "%02d/%02d/%04d", t.Day(), t.Month(), t.Year())
case 't':
fmt.Fprintf(f, "%02d:%02d:%02d", t.Hour(), t.Minute(), t.Second())
case 'v':
if f.Flag('+') {
fmt.Fprintf(f, time.Time(d).String())
} else {
fmt.Fprintf(f, "%s", t.Format("2006-01-02"))
}
default:
fmt.Fprintf(f, "%%!%c(MyDate=%s)", verb, t.Format(time.RFC3339))
}
}
func main() {
d := MyDate(time.Date(2023, 5, 15, 14, 30, 0, 0, time.UTC))
fmt.Printf("%d\n", d) // 15/05/2023
fmt.Printf("%t\n", d) // 14:30:00
fmt.Printf("%v\n", d) // 2023-05-15
fmt.Printf("%+v\n", d) // 2023-05-15 14:30:00 +0000 UTC
}
MyDate 类型为日期提供自定义格式。它支持 'd' 用于日期,'t' 用于时间,以及带 + 标志的 'v' 用于详细输出。
二进制数据格式化
fmt.Formatter 可以以不同的表示形式格式化二进制数据。此示例显示了十六进制转储和二进制输出。
package main
import (
"fmt"
"strings"
)
type Binary []byte
func (b Binary) Format(f fmt.State, verb rune) {
switch verb {
case 'x', 'X':
for _, byteVal := range b {
fmt.Fprintf(f, "%02x", byteVal)
}
case 'b':
for _, byteVal := range b {
fmt.Fprintf(f, "%08b ", byteVal)
}
case 'v':
if f.Flag('#') {
fmt.Fprintf(f, "Binary{0x%x}", b)
} else {
fmt.Fprintf(f, "%x", b)
}
default:
fmt.Fprintf(f, "%%!%c(Binary=%x)", verb, b)
}
}
func main() {
data := Binary{0xDE, 0xAD, 0xBE, 0xEF}
fmt.Printf("%x\n", data) // deadbeef
fmt.Printf("%X\n", data) // DEADBEEF
fmt.Printf("%b\n", data) // 11011110 10101101 10111110 11101111
fmt.Printf("%#v\n", data) // Binary{0xdeadbeef}
}
Binary 类型将字节切片格式化为十六进制或二进制。当与 'v' 动词一起使用时,'#' 标志会触发 Go 语法表示。
来源
本教程通过不同类型的自定义格式的实际示例,涵盖了 Go 中的 fmt.Formatter 接口。