这个问题在这里已有答案:
我尝试在我的类型上实现 Stringer
接口,如下所示:
package main
import (
"fmt"
)
type IPAddr [4]byte
// TODO: Add a "String() string" method to IPAddr.
func (o IPAddr) String() string {
return fmt.Sprintf("%v.%v.%v.%v", o[0], o[1], o[2], o[3])
}
func main() {
hosts := map[string]IPAddr{
"loopback": {127, 0, 0, 1},
"googleDNS": {8, 8, 8, 8},
}
for name, ip := range hosts {
fmt.Printf("%v: %v\n", name, ip)
fmt.Printf("%v\n", ip.String())
}
}
在上面的代码中,我使用值接收器来实现 String()
方法 . Printf
识别了我的实现并在我的类型上调用了正确的 String
函数 .
输出:
googleDNS: 8.8.8.8
8.8.8.8
loopback: 127.0.0.1
127.0.0.1
然后我更新了我的代码以使用指针接收器:
func (o *IPAddr) String() string {
return fmt.Sprintf("%v.%v.%v.%v", o[0], o[1], o[2], o[3])
}
更新代码的输出:
loopback: [127 0 0 1]
127.0.0.1
googleDNS: [8 8 8 8]
8.8.8.8
Printf
方法不再调用我的 String
方法 . 输出告诉我 Printf
使用该类型的默认 String
方法 . 但是,当我调用 ip.String()
时,我的方法被使用了 .
有人可以向我解释这种行为吗?据我所知,我们可以通过值接收器和指针接收器实现接口方法 .
谢谢 .
2 回答
问题是你的 Map 包含
IPAddr
类型,它没有String()
函数,只有*IPAddr
. 这意味着您将 not 接口传递给打印功能,因此它使用默认打印 .Go中的一个特点是你仍然可以做到以下几点:
因为在这种情况下,Go足够聪明,知道它可以在变量的地址上调用
String()
函数 . Go可以自动获取变量的地址以调用其上的函数 .另一方面,您甚至不允许在 Map 中包含的
IPAddr
上调用String()
,因为使用[]
从map
中获取内容会返回无法寻址的副本 . Here is an example来说明这些属性:%v
转换说明符将读取满足Stringer
接口的任何方法 . 但是,要实现此目的,该方法必须存在于值的方法集中 .对于
T
类型的值,其方法集包含接收该类型T
的值的任何方法:对于
*T
类型的指针,其方法集包含接收类型为T
的值的方法和类型为*T
的指针:在
main
中,您有一个标识为ip
且值为IPAddr
的值,因此上面的第一组注释代码适用 .您的第一个示例将起作用,因为
String
方法的方法接收器具有类型IPAddr
.在第二个示例中,
String
方法的方法接收器具有类型*IPAddr
,这意味着它不在ip
的方法集中,其类型为IPAddr
.综上所述:
你可能想知道为什么会这样 . 事实证明,某些值可能无法寻址,因此类型为
*IPAddr
的方法接收器无法接收没有地址的值 . 例如,尝试使用*IPAddr
方法接收器执行IPAddr{}.String()
. 它将无法编译,因为文字值没有地址 . 如果你改为使用(&IPAddr{}).String()
,它会工作,因为现在你有一个使用&IPAddr{}
创建的指针*IPAddr
,如果你使用了非指针接收器IPAddr
,那么无论IPAddr
是否可寻址它都可以工作 .