首页 文章

使用值或指针接收器实现Stringer接口[重复]

提问于
浏览
1

这个问题在这里已有答案:

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

  • 1

    问题是你的 Map 包含 IPAddr 类型,它没有 String() 函数,只有 *IPAddr . 这意味着您将 not 接口传递给打印功能,因此它使用默认打印 .

    Go中的一个特点是你仍然可以做到以下几点:

    var ip IPAddr
    ip.String()
    

    因为在这种情况下,Go足够聪明,知道它可以在变量的地址上调用 String() 函数 . Go可以自动获取变量的地址以调用其上的函数 .

    另一方面,您甚至不允许在 Map 中包含的 IPAddr 上调用 String() ,因为使用 []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())
    }
    
  • 2

    %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 方法接收器执行 IPAddr{}.String() . 它将无法编译,因为文字值没有地址 . 如果你改为使用 (&IPAddr{}).String() ,它会工作,因为现在你有一个使用 &IPAddr{} 创建的指针 *IPAddr ,如果你使用了非指针接收器 IPAddr ,那么无论 IPAddr 是否可寻址它都可以工作 .

相关问题