在为类型定义方法时,如果没有避免拷贝或在方法中修改对象的需求,那选择指针接收器和值接收器区别不大。大多数情况下,我们可以通过指针直接调值接收器方法,也可以通过值直接调指针接收器方法,go会为我们自动做值和指针之间的转换:
type A struct {
d int
}
func (a A) print() {
fmt.Println(a.d)
}
type B struct {
d int
}
func (b *B) print() {
fmt.Println(b.d)
}
func main() {
a := A{d: 111}
pa := &a
b := B{d: 222}
pb := &b
a.print()
pa.print()
b.print()
pb.print()
}
但是当我们是为了实现接口而定义方法时,指针接收器和值接收器之间的区别就不能忽略了:
type X struct {
d int
}
func (x X) String() string {
return fmt.Sprintf("X{d:%d}", x.d)
}
type Y struct {
d int
}
func (y *Y) String() string {
return fmt.Sprintf("Y{d:%d}", y.d)
}
func print(v fmt.Stringer) {
fmt.Println(v)
}
func main() {
x := X{d: 111}
px := &x
y := Y{d: 222}
py := &y
print(x)
print(px)
print(y) // error!
print(py)
}
编译上面的代码时,print(y)
一行会报错:
./main.go:32:7: cannot use y (type Y) as type fmt.Stringer in argument to print:
Y does not implement fmt.Stringer (String method has pointer receiver)
提示不能把类型Y
的实参值赋给类型fmt.Stringer
的形参,原因是类型Y
没有实现fmt.Stringer
接口。
打印一下X
、*X
、Y
、*Y
类型都实现了哪些方法,以及是否实现了fmt.Stringer
接口:
func printMethod(v interface{}) {
defer fmt.Println("-----")
t := reflect.TypeOf(v)
fmt.Printf("Type: %v\n", t)
if t == nil {
fmt.Printf(" No method\n")
return
}
methodCount := t.NumMethod()
fmt.Printf(" Method count: %d\n", methodCount)
for i := 0; i < methodCount; i++ {
fmt.Printf(" %d: %v\n", i, t.Method(i))
}
stringerType := reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
fmt.Printf(" Implements fmt.Stringer: %v\n", t.Implements(stringerType))
}
...
printMethod(x)
printMethod(px)
printMethod(y)
printMethod(py)
输出为:
Type: main.X
Method count: 1
0: {String func(main.X) string <func(main.X) string Value> 0}
Implements fmt.Stringer: true
-----
Type: *main.X
Method count: 1
0: {String func(*main.X) string <func(*main.X) string Value> 0}
Implements fmt.Stringer: true
-----
Type: main.Y
Method count: 0
Implements fmt.Stringer: false
-----
Type: *main.Y
Method count: 1
0: {String func(*main.Y) string <func(*main.Y) string Value> 0}
Implements fmt.Stringer: true
-----
唯独main.Y
类型没有实现String
方法,所以也就没有实现fmt.Stringer
接口。这反映了go的一个特性:对于类型*T
,其自动实现了类型T
实现的方法;而对于类型T
,其没有自动实现*T
实现的方法;类型T
可以调用类型*T
的方法只是go的一个语法糖。
在某些情况下,我们可以利用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}
我们可以为Foo
类型实现fmt.Stringer
接口来使fmt.Printf
打印出Bar中的F字段:
// 方式1
func (f Foo) String() string {
return fmt.Sprintf("{A: %v}", f.A)
}
输出为:
{F:{A: 1}}
实际上String
方法还可以实现的更简单一些:
// 形式2
func (f *Foo) String() string {
return fmt.Sprintf("&%+v", *f)
}
在方式2中,*Foo
类型实现了fmt.Stringer
接口,而Foo
类型没有实现,所以打印*f
时不会调用到(*Foo).String
方法,就不会有递归调用的问题了。而下面这种实现就会发生递归的问题:
// 方式3
func (f Foo) String() string {
return fmt.Sprintf("&%+v", f)
}
在方式3中,fmt.Sprintf
函数在打印f
时会发现f
实现了fmt.Stringer
接口,因此会调用f.String
进行字符化,而f.String
又会调用到fmt.Sprintf
方法,这就产生了无限递归的问题:
#0 main.(*Foo).String (~r0=...) at /mnt/d/wsl/code/godive/main.go:16
#1 0x0000000000489662 in fmt.(*pp).handleMethods (p=0xc00009ed00, verb=118, handled=true) at /usr/lib/go-1.13/src/fmt/print.go:630
#2 0x0000000000489c26 in fmt.(*pp).printArg (p=0xc00009ed00, arg=..., verb=118) at /usr/lib/go-1.13/src/fmt/print.go:713
#3 0x000000000048d5eb in fmt.(*pp).doPrintf (p=0xc00009ed00, format=..., a=...) at /usr/lib/go-1.13/src/fmt/print.go:1030
#4 0x0000000000486866 in fmt.Sprintf (format=..., a=..., ~r2=...) at /usr/lib/go-1.13/src/fmt/print.go:219
#5 0x000000000048ea87 in main.(*Foo).String (~r0=...) at /mnt/d/wsl/code/godive/main.go:16
#6 0x0000000000489662 in fmt.(*pp).handleMethods (p=0xc00009ec30, verb=118, handled=true) at /usr/lib/go-1.13/src/fmt/print.go:630
#7 0x0000000000489c26 in fmt.(*pp).printArg (p=0xc00009ec30, arg=..., verb=118) at /usr/lib/go-1.13/src/fmt/print.go:713
#8 0x000000000048d5eb in fmt.(*pp).doPrintf (p=0xc00009ec30, format=..., a=...) at /usr/lib/go-1.13/src/fmt/print.go:1030
#9 0x0000000000486866 in fmt.Sprintf (format=..., a=..., ~r2=...) at /usr/lib/go-1.13/src/fmt/print.go:219
#10 0x000000000048ea87 in main.(*Foo).String (~r0=...) at /mnt/d/wsl/code/godive/main.go:16
#11 0x0000000000489662 in fmt.(*pp).handleMethods (p=0xc00009eb60, verb=118, handled=true) at /usr/lib/go-1.13/src/fmt/print.go:630
#12 0x0000000000489c26 in fmt.(*pp).printArg (p=0xc00009eb60, arg=..., verb=118) at /usr/lib/go-1.13/src/fmt/print.go:713
#13 0x000000000048d5eb in fmt.(*pp).doPrintf (p=0xc00009eb60, format=..., a=...) at /usr/lib/go-1.13/src/fmt/print.go:1030
#14 0x0000000000486866 in fmt.Sprintf (format=..., a=..., ~r2=...) at /usr/lib/go-1.13/src/fmt/print.go:219
#15 0x000000000048ea87 in main.(*Foo).String (~r0=...) at /mnt/d/wsl/code/godive/main.go:16
#16 0x0000000000489662 in fmt.(*pp).handleMethods (p=0xc00009ea90, verb=118, handled=true) at /usr/lib/go-1.13/src/fmt/print.go:630
#17 0x000000000048cb48 in fmt.(*pp).printValue (p=0xc00009ea90, value=..., verb=118, depth=1) at /usr/lib/go-1.13/src/fmt/print.go:727
#18 0x000000000048bf85 in fmt.(*pp).printValue (p=0xc00009ea90, value=..., verb=118, depth=0) at /usr/lib/go-1.13/src/fmt/print.go:810
#19 0x0000000000489cd4 in fmt.(*pp).printArg (p=0xc00009ea90, arg=..., verb=118) at /usr/lib/go-1.13/src/fmt/print.go:716
#20 0x000000000048d5eb in fmt.(*pp).doPrintf (p=0xc00009ea90, format=..., a=...) at /usr/lib/go-1.13/src/fmt/print.go:1030
#21 0x0000000000486762 in fmt.Fprintf (w=..., format=..., a=..., n=<optimized out>, err=...) at /usr/lib/go-1.13/src/fmt/print.go:204
#22 0x000000000048e9d3 in fmt.Printf (format=..., a=..., n=<optimized out>, err=...) at /usr/lib/go-1.13/src/fmt/print.go:213
#23 main.main () at /mnt/d/wsl/code/godive/main.go:20