我尝试在我的类型上实现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()
时,使用了我的方法。
有人可以向我解释这种行为吗?据我所知,我们可以通过值接收器和指针接收器来实现接口方法。
谢谢。
答案 0 :(得分:2)
问题是,您的地图包含类型IPAddr
,该类型没有String()
函数,只有*IPAddr
有。这意味着您不会不将Stringer
界面传递给打印功能,因此它将使用默认打印。
Go的一个独特之处在于您仍然可以执行以下操作:
var ip IPAddr
ip.String()
因为在这种情况下,Go足够聪明,知道它可以在变量的地址上调用String()
函数。 Go可以自动获取变量的地址以在其上调用函数。
另一方面,甚至不允许您在地图中包含的String()
上调用IPAddr
,因为使用map
从[]
中取出东西会返回不可寻址的副本。 Here is an example来说明这些属性:
package main
import "fmt"
type IPAddr [4]byte
func (o *IPAddr) String() string {
return fmt.Sprintf("%v.%v.%v.%v", o[0], o[1], o[2], o[3])
}
func main() {
var ip = IPAddr{1, 2, 3, 4}
// printing the value does not call String() because we pass type IPAddr,
// not type *IPAddr
fmt.Printf("%v\n", ip)
// but we can call String() on the variable because Go knows how to get its
// address
fmt.Println(ip.String())
m := map[int]IPAddr{1: IPAddr{1, 2, 3, 4}}
fmt.Println(m[1])
// the following is a compile-time error because Go cannot take the address
// of things in the map, because the []-operator returns only a copy of the
// IPAddr
//fmt.Println(m[1].String())
}
答案 1 :(得分:1)
%v
转换说明符将读取满足Stringer
接口的任何方法。为此,该方法必须存在于值的方法集中。
对于类型为T
的值,其方法集包含任何接收到类型为T
的值的方法:
func (t T) Foo() // in the method set of T
func (t *T) Bar() // not in the method set of T
对于类型为*T
的指针,其方法集同时包含接收类型为T
的值的方法和类型为*T
的指针:
func (t T) Foo() // in the method set of *T
func (t *T) Bar() // in the method set of *T
在main
中,您具有一个类型为ip
的值IPAddr
,因此适用上面的第一组注释代码。
您的第一个示例将起作用,因为String
方法的方法接收者的类型为IPAddr
。
在第二个示例中,String
方法的方法接收者的类型为*IPAddr
,这意味着它不在ip
方法类型的IPAddr
的方法集中
总结:
| String() Method | fmt.Print, fmt.Printf, etc.
Input Type | Receiver | calls String() implicitly
========== | =============== | ===========================
*IPAddr | IPAddr | Yes
| *IPAddr | Yes
---------- + --------------- + ---------------------------
IPAddr | IPAddr | Yes
| *IPAddr | No
您可能想知道为什么会这样。事实证明,某些值可能无法寻址,因此类型为*IPAddr
的方法接收器无法接收没有地址的值。例如,尝试使用IPAddr{}.String()
方法接收者执行*IPAddr
。由于文字值没有地址,它将无法编译。如果您改用(&IPAddr{}).String()
,那么它将起作用,因为现在您有了使用*IPAddr
创建的指针&IPAddr{}
,并且如果您使用了非指针接收器IPAddr
,那么它将无论IPAddr
是否可寻址。