ZetCode

Golang fmt.Formatter 接口

最后修改时间 2025 年 5 月 8 日

本教程解释了如何在 Go 中使用 fmt.Formatter 接口。我们将通过自定义格式的实际示例来介绍接口基础知识。

fmt.Formatter 接口允许类型控制它们的格式。它通过支持格式动词和标志,比 Stringer 接口提供了更大的灵活性。

在 Go 中,当您需要超出简单字符串转换的自定义格式行为时,会使用 fmt.Formatter。它与 Printf 和 Sprintf 等所有 fmt 打印函数一起工作。

基本的 fmt.Formatter 接口定义

fmt.Formatter 接口要求实现一个方法。此示例显示了接口定义和基本实现。
注意: Format 方法让您可以完全控制格式。

basic_formatter.go
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 参数提供了对格式标志的访问。此示例展示了如何处理宽度和精度规范。

width_precision.go
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 接口提供标志信息。此示例演示了格式化程序中的自定义标志处理。

flag_handling.go
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。此示例展示了它们如何协同工作。

stringer_combine.go
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 可用于创建自定义复数格式。此示例显示了极坐标表示。

complex_format.go
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 可以实现灵活的日期格式。此示例显示了带有不同动词的自定义日期输出。

date_format.go
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 可以以不同的表示形式格式化二进制数据。此示例显示了十六进制转储和二进制输出。

binary_format.go
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 包文档

本教程通过不同类型的自定义格式的实际示例,涵盖了 Go 中的 fmt.Formatter 接口。

作者

我叫 Jan Bodnar,我是一位热情的程序员,拥有丰富的编程经验。我自 2007 年以来一直在撰写编程文章。迄今为止,我已撰写了 1400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出所有 Golang 教程