ZetCode

Golang fmt.State 接口

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

本教程讲解如何在 Go 中使用 fmt.State 接口。我们将通过自定义格式化的实际示例来介绍接口方法。

fmt.State 接口用于 Go 中的自定义格式化。它提供了对格式标志和选项的访问。实现者可以检查格式指令并写入格式化输出。

在 Go 中,fmt.State 通常与 fmt.Formatter 接口一起使用。它允许类型控制它们如何使用 %v%s 等格式动词或自定义动词进行打印。

fmt.State 接口基本定义

fmt.State 接口有三个方法。此示例显示了接口定义和基本用法。

注意:实现者必须处理所有三个方法。
state_interface.go
package main

import "fmt"

// fmt.State interface definition:
/*
type State interface {
    Write(b []byte) (n int, err error)
    Width() (wid int, ok bool)
    Precision() (prec int, ok bool)
    Flag(c int) bool
}
*/

func main() {
    // The fmt package passes a fmt.State implementation
    // to Formatter.Format method when formatting values
    fmt.Println("See examples below for practical usage")
}

该接口提供宽度、精度和标志信息。Write 方法输出格式化文本。这些在自定义格式化器中结合使用。

实现自定义格式化器

我们可以创建实现了 fmt.Formatter 的类型,并使用 fmt.State。此示例展示了一个基本的自定义格式化器实现。

custom_formatter.go
package main

import (
    "fmt"
    "strings"
)

type MyType struct {
    value string
}

func (m MyType) Format(f fmt.State, verb rune) {
    switch verb {
    case 'v':
        if f.Flag('+') {
            fmt.Fprintf(f, "MyType{value: %q}", m.value)
        } else {
            fmt.Fprintf(f, "%s", m.value)
        }
    case 's':
        fmt.Fprintf(f, "%s", strings.ToUpper(m.value))
    default:
        fmt.Fprintf(f, "%%!%c(MyType=%s)", verb, m.value)
    }
}

func main() {
    val := MyType{"hello"}
    fmt.Printf("%v\n", val)       // hello
    fmt.Printf("%+v\n", val)      // MyType{value: "hello"}
    fmt.Printf("%s\n", val)       // HELLO
    fmt.Printf("%x\n", val)       // %!x(MyType=hello)
}

Format 方法处理不同的格式动词。它使用 fmt.State 来检查标志并写入输出。这提供了对格式化的完全控制。

使用宽度和精度

WidthPrecision 方法提供格式化详细信息。此示例演示了它们在自定义格式化中的用法。

width_precision.go
package main

import "fmt"

type BoxedString string

func (b BoxedString) Format(f fmt.State, verb rune) {
    width, hasWidth := f.Width()
    prec, hasPrec := f.Precision()
    
    if !hasWidth {
        width = len(b) + 4
    }
    if !hasPrec {
        prec = len(b)
    }
    
    if prec > len(b) {
        prec = len(b)
    }
    
    topBottom := strings.Repeat("*", width)
    content := fmt.Sprintf("* %.*s %*s *", prec, b, 
        width-prec-5, "")
    
    fmt.Fprintln(f, topBottom)
    fmt.Fprintln(f, content)
    fmt.Fprint(f, topBottom)
}

func main() {
    msg := BoxedString("Hello, World!")
    fmt.Printf("%v\n", msg)
    fmt.Printf("%10.5v\n", msg)
}

格式化器使用宽度和精度来控制输出。它在字符串周围创建一个具有动态大小的框。不同的格式动词会产生不同的输出布局。

检查格式标志

Flag 方法检查 '+'、'-' 或 '#' 等格式标志。此示例展示了如何在格式化中处理不同的标志。

format_flags.go
package main

import (
    "fmt"
    "strconv"
)

type Temperature float64

func (t Temperature) Format(f fmt.State, verb rune) {
    switch verb {
    case 'v', 'f', 'g':
        if f.Flag('#') {
            fmt.Fprintf(f, "Temperature(%v)", float64(t))
            return
        }
        if f.Flag('+') {
            fmt.Fprintf(f, "%+.2f°C", float64(t))
            return
        }
        fmt.Fprintf(f, "%.2f°C", float64(t))
    case 's':
        fmt.Fprintf(f, strconv.FormatFloat(float64(t), 'f', -1, 64))
    default:
        fmt.Fprintf(f, "%%!%c(Temperature=%v)", verb, t)
    }
}

func main() {
    temp := Temperature(23.456)
    fmt.Printf("%v\n", temp)    // 23.46°C
    fmt.Printf("%+#v\n", temp)  // Temperature(23.456)
    fmt.Printf("%+v\n", temp)   // +23.46°C
    fmt.Printf("%s\n", temp)    // 23.456
}

格式化器使用 Flag() 检查 '#' 和 '+' 标志。它根据存在的标志产生不同的输出。这提供了灵活的格式化选项。

自定义动词处理

我们可以使用 fmt.State 定义自定义格式动词。此示例展示了如何实现自定义的 'b' 动词来进行二进制输出。

custom_verb.go
package main

import (
    "fmt"
    "strconv"
)

type BinaryInt int

func (b BinaryInt) Format(f fmt.State, verb rune) {
    switch verb {
    case 'b':
        width, _ := f.Width()
        s := strconv.FormatInt(int64(b), 2)
        if width > len(s) {
            s = fmt.Sprintf("%0*s", width, s)
        }
        fmt.Fprint(f, s)
    case 'v', 'd':
        fmt.Fprintf(f, "%d", int(b))
    default:
        fmt.Fprintf(f, "%%!%c(BinaryInt=%d)", verb, b)
    }
}

func main() {
    num := BinaryInt(42)
    fmt.Printf("%b\n", num)     // 101010
    fmt.Printf("%8b\n", num)    // 00101010
    fmt.Printf("%v\n", num)     // 42
    fmt.Printf("%x\n", num)     // %!x(BinaryInt=42)
}

自定义的 'b' 动词以二进制格式输出整数。它会考虑宽度说明符进行填充。其他动词将回退到默认格式化或错误消息。

与 Stringer 接口结合使用

fmt.State 可以与 fmt.Stringer 结合使用。此示例展示了如何提供不同的字符串表示形式。

stringer_combination.go
package main

import (
    "fmt"
    "strings"
)

type Secret string

func (s Secret) String() string {
    return "*****" // Default string representation
}

func (s Secret) Format(f fmt.State, verb rune) {
    switch verb {
    case 'v':
        if f.Flag('#') {
            fmt.Fprintf(f, "Secret(%q)", string(s))
        } else if f.Flag('+') {
            fmt.Fprintf(f, "%s (length: %d)", s.String(), len(s))
        } else {
            fmt.Fprintf(f, "%s", s.String())
        }
    case 's':
        fmt.Fprintf(f, "%s", s.String())
    case 'q':
        fmt.Fprintf(f, "%q", s.String())
    case 'x':
        fmt.Fprintf(f, "%x", []byte(s))
    default:
        fmt.Fprintf(f, "%%!%c(Secret=%s)", verb, s.String())
    }
}

func main() {
    sec := Secret("password123")
    fmt.Println(sec)            // *****
    fmt.Printf("%#v\n", sec)    // Secret("password123")
    fmt.Printf("%+v\n", sec)    // ***** (length: 11)
    fmt.Printf("%x\n", sec)     // 70617373776f7264313233
}

该类型提供了一个简单的 String() 方法用于基本输出。Format 方法提供了详细的格式化选项。这种组合在输出表示方面提供了灵活性。

高级 State 用法

对于复杂的格式化,我们可以充分利用 fmt.State 方法。此示例展示了高级的宽度、精度和标志处理。

advanced_state.go
package main

import (
    "fmt"
    "strings"
)

type ProgressBar float64 // 0.0 to 1.0

func (p ProgressBar) Format(f fmt.State, verb rune) {
    width := 20
    if w, ok := f.Width(); ok {
        width = w
    }
    
    fill := int(float64(width) * float64(p))
    if fill > width {
        fill = width
    }
    
    bar := strings.Repeat("=", fill) + 
           strings.Repeat(" ", width-fill)
    
    percent := int(100 * float64(p))
    
    if f.Flag('+') {
        fmt.Fprintf(f, "[%s] %3d%%", bar, percent)
    } else if f.Flag('-') {
        fmt.Fprintf(f, "%3d%% [%s]", percent, bar)
    } else {
        fmt.Fprintf(f, "[%s]", bar)
    }
}

func main() {
    p := ProgressBar(0.65)
    fmt.Printf("%v\n", p)       // [============      ]
    fmt.Printf("%+v\n", p)      // [============      ]  65%
    fmt.Printf("%-v\n", p)      // 65% [============      ]
    fmt.Printf("%30v\n", p)     // [==========================    ]
    fmt.Printf("%+30v\n", p)    // [==========================    ]  65%
}

进度条格式化器会根据宽度动态调整。它使用标志来控制百分比显示的位置。这展示了 fmt.State 在丰富格式化方面的综合用法。

来源

Go fmt.State 文档

本教程通过自定义格式化实现的实际示例,介绍了 Go 中的 fmt.State 接口。

作者

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

列出所有 Golang 教程