使用值或指针接收器实现Stringer接口

时间:2018-09-09 03:00:49

标签: go

我尝试在我的类型上实现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 个答案:

答案 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是否可寻址。

相关问题