Golang fmt.State 接口
最后修改时间 2025 年 5 月 8 日
本教程讲解如何在 Go 中使用 fmt.State 接口。我们将通过自定义格式化的实际示例来介绍接口方法。
fmt.State 接口用于 Go 中的自定义格式化。它提供了对格式标志和选项的访问。实现者可以检查格式指令并写入格式化输出。
在 Go 中,fmt.State 通常与 fmt.Formatter 接口一起使用。它允许类型控制它们如何使用 %v、%s 等格式动词或自定义动词进行打印。
fmt.State 接口基本定义
fmt.State 接口有三个方法。此示例显示了接口定义和基本用法。
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。此示例展示了一个基本的自定义格式化器实现。
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 来检查标志并写入输出。这提供了对格式化的完全控制。
使用宽度和精度
Width 和 Precision 方法提供格式化详细信息。此示例演示了它们在自定义格式化中的用法。
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 方法检查 '+'、'-' 或 '#' 等格式标志。此示例展示了如何在格式化中处理不同的标志。
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' 动词来进行二进制输出。
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 结合使用。此示例展示了如何提供不同的字符串表示形式。
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 方法。此示例展示了高级的宽度、精度和标志处理。
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 接口。