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