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
接口。