前言
fmt包实现了类似C语言printf和scanf的格式化I/O。格式化动作('verb')源自C语言但更简单。本文将介绍 Stringer, GoStringer, State, Formatter这几个接口的作用。
Stringer 接口
type Stringer interface {
String() string
}
如果一个对象实现了这个Stringer
接口,那么通过%s
,%v
打印,这时候会判断这个对象是否实现了Stringer
接口,如果实现了,就调用对象的String
方法,怎么感觉有点像python中的 __str__
方法。
package main
import "fmt"
type person struct {
Name string
age int
}
func (p *person) String() string {
return p.Name
}
func main() {
p := &person{"jack",22}
println(p) // 0xc000090000,这个打印的是地址
fmt.Println(p) // jack
fmt.Printf("%vn",p) // jack
fmt.Printf("%sn",p) // jack
}
对于内建函数println()
,print()
是不能调用到String
方法的。
对于某些结构体,当某些字段不希望在打印的时候输出时,可以实现Stringer
接口,达到隐藏某些字段的目的。
GoStringer 接口
type GoStringer interface {
GoString() string
}
实现了GoStringer
接口的对象,当采用 %#v
格式化输出时(其他的形式会不会呢?),会调用GoString方法来生成输出定义了该类型值的go语法表示。就是说这个是go特有的,比如&main.person{Name:"jack", age:22}
这种样式的就是go语法来表示一个对象,当然我们可以自己定义。
package main
import (
"encoding/json"
"fmt"
)
type person struct {
Name string
age int
}
func (p *person) GoString() string {
b, _ :=json.Marshal(p)
return string(b)
}
func main() {
p := &person{"jack",22}
fmt.Printf("%vn",p) // &{jack 22}
fmt.Printf("%#vn",p) // {"Name":"jack"} , GoString的输出
fmt.Printf("%+vn",p) // &{Name:jack age:22}
}
这里我们通过实现
GoStringer
接口,并返回对象 json 序列化后的字符串来达到改写对象的go语法表示。
State 接口
type State interface {
// Write方法用来写入格式化的文本
Write(b []byte) (ret int, err error)
// Width返回宽度值,及其是否被设置
Width() (wid int, ok bool)
// Precision返回精度值,及其是否被设置
Precision() (prec int, ok bool)
// Flag报告是否设置了flag c(一个字符,如+、-、#等)
Flag(c int) bool
}
在学习 State
接口之前,我们先来看一下,go的格式化语法
![6add52a42a5ce991ca6469ae2dd09cc4.png](https://i-blog.csdnimg.cn/blog_migrate/4e65de50001472c922999bb74f4dc0f9.png)
%
表示格式化的开始位置%
后面紧跟的+
号,表示flags
+
后面的3.2
表示宽度Width
和精度Precision
f
为占位符verb
那么go格式化语法包括了 开始位置
,flags
,宽度
,精度
,占位符
五个部分组成,State
接口包含了其中三项信息。在解析格式化字符串的时候,会一个一个解析,当解析完成一个这样的%+3.2f
整体结构以后,数据保存到State
,下一步调用printArg
进行格式化,并将格式化后的数据写入buffer
,所有的结构解析完成之后写入到io.Writer
,并在控制台输出(后面会具体讨论代码是怎么实现的)。
Formatter 接口
type Formatter interface {
Format(f State, c rune)
}
Formatter
用于实现自定义格式化方法。可通过在自定义结构体中实现 Format
方法来实现这个目的。标准库中使用在 /usr/local/go/src/fmt/print.go:580
,会首先判断结构体是否实现了 Formatter
接口,实现了则利用自定义 Formatter 格式化参数。未实现,则最大程度的利用 go语法格式 GoStringer
默认规则去格式化参数,其次是 error
和 Stringer
接口。
我们来看一下大厂 B站是怎么实现这个接口的
// Format is a support routine for fmt.Formatter. It accepts the decimal
// formats 'd' and 'f', and handles both equivalently.
// Width, precision, flags and bases 2, 8, 16 are not supported.
func (x *Dec) Format(s fmt.State, ch rune) {
if ch != 'd' && ch != 'f' && ch != 'v' && ch != 's' {
fmt.Fprintf(s, "%%!%c(dec.Dec=%s)", ch, x.String())
return
}
fmt.Fprintf(s, x.String())
}
是不是很简单?
小结
本小节介绍了fmt包中的几个基础接口和使用。下一篇,我们将深入fmt包的实现。