在Go中,一个结构体如果含有一个指针字段,在默认情况下,使用fmt.Print
系列函数打印这个结构体时,并不会打印这个指针指向的值。因此,对于下面的代码:
type Foo struct {
A int
}
type Bar struct {
F *Foo
}
func main() {
b := Bar{&Foo{1}}
fmt.Printf("%+v\n", b)
}
其输出为:
{F:0xc0000140a0}
map
也是类似的,如果map
的value类型为指针,fmt.Print
系列函数同样不会打印指针指向的值。
如果想要把指针的值打印出来,那么这个指针指向的类型需要实现fmt.Stringer
接口:
type Stringer interface {
String() string
}
对于下面的代码:
type Foo struct {
A int
}
func (f *Foo) String() string {
return fmt.Sprintf("{A: %v}", f.A)
}
type Bar struct {
F *Foo
}
func main() {
b := Bar{&Foo{1}}
fmt.Printf("%+v\n", b)
}
其输出为:
{F:{A: 1}}
为什么默认不打印指针字段指向的值?是因为Go的反射机制有什么限制吗?并不是,查看Go SDK的源码,我们会发现fmt.Print
在打印指针时有一段特别的逻辑:fmt.Print
只打印直接传入的指针指向的值,且这个值的类型必须是数组/切片/结构体/map。在print.go的printValue
函数中,有这样一段代码:
case reflect.Ptr:
// pointer to array or slice or struct? ok at top level
// but not embedded (avoid loops)
if depth == 0 && f.Pointer() != 0 {
switch a := f.Elem(); a.Kind() {
case reflect.Array, reflect.Slice, reflect.Struct, reflect.Map:
p.buf.writeByte('&')
p.printValue(a, verb, depth+1)
return
}
}
fallthrough
其中depth
是当前正在打印的值的层次,对于直接传入fmt.Print
的值,depth
设为0。如果传入fmt.Print
的值是一个结构体,Go会尝试遍历打印结构体中所有的字段,在打印这些字段时depth
会被设为1。depth
为1的字段也可能是个结构体,结构体中还有字段,依次类推,depth
还可以是2、3、4……如果一个要打印的值类型为指针,只有depth
==0时才会尝试打印指针指向的值。至于为什么有这样的逻辑,代码中的注释写的很清楚了:为了避免陷入循环。试想一下,如果没有这样的逻辑限制,在打印下面的b
时会发生什么事情呢?
type Baz struct {
i interface{}
}
func main() {
b := Baz{}
b.i = &b
fmt.Printf("%+v\n", b)
}