Stringer 方法需要值

Ela*_*arR 5 methods interface go

Go FAQ 回答了一个关于方法中按值与按指针接收器定义选择的问题。该答案中的陈述之一是:

如果该类型的某些方法必须有指针接收器,其余的也应该如此,因此无论如何使用该类型,方法集都是一致的。

这意味着,如果我对一个数据类型有一些改变数据的方法,因此需要按指针接收器,我应该对为该数据类型定义的所有方法使用按指针接收器。

另一方面,"fmt"通过 value调用接口中String()定义的方法。如果用接收器按指针定义方法,则当关联的数据类型作为参数提供给(或其他格式化方法)时,将不会调用该方法。这让人们别无选择,只能使用接收器按值实现该方法。StringerString()fmt.PrintlnfmtString()

正如常见问题解答所建议的那样,在满足接口fmt要求的同时,如何与按值与按指针的选择保持一致Stringer

编辑:

为了强调我提到的问题的本质,考虑一个数据类型,其中包含一组用接收者按值定义的方法(包括 String())。然后一个人希望添加一个额外的方法来改变该数据类型 - 所以他用接收者按指针定义它,并且(为了保持一致,每个常见问题解答)他还更新了该数据类型的所有其他方法以供使用- 指针接收器。此更改对使用此数据类型方法的任何代码的影响为零 - 但用于调用fmt格式化函数(现在需要将指针传递给变量而不是其值,就像更改之前一样)。因此,一致性要求仅在fmt. 需要调整提供变量的方式fmt.Println (或类似功能)基于接收器类型打破了轻松重构一个包的能力。

lea*_*bop 2

为了强调我提到的问题的本质,考虑一种情况,其中一个数据类型具有一组用接收者按值定义的方法(包括 String())。然后,有人希望添加一种改变该数据类型的附加方法 - 因此他用接收者指针定义它,并且(为了保持一致,根据常见问题解答答案)他还更新了该数据类型的所有其他方法以供使用-指针接收器。此更改对使用此数据类型的方法的任何代码影响为零 - 但对于 fmt 格式化函数的调用(现在需要像更改之前一样传递指向变量的指针而不是其值)。

这不是真的。所有interface这些和一些类型断言也会受到影响 - 这就是 fmt 受到影响的原因。例如:

package main

import (
    "fmt"
)

type I interface {
    String() string
}

func (t t) String() string { return "" }

func (p *p) String() string { return "" }

type t struct{}
type p struct{}

func S(i I) {}

func main() {
    fmt.Println("Hello, playground")
    T := t{}
    P := p{}
    _ = P
    S(T)
    //S(P) //fail
}
Run Code Online (Sandbox Code Playgroud)

要从根本上理解这一点,您应该知道指针方法和值方法从本质上来说是不同的。然而,为了方便起见,就像省略 一样;,golang 编译器会查找使用没有指针的指针方法的情况并将其改回来。

正如这里所解释的: https: //tour.golang.org/methods/6

那么回到最初的问题:指针方法的一致性。如果您更仔细地阅读常见问题解答,您会发现这是考虑使用值或指针方法的最后一部分。您可以在标准库示例中找到反例container/heap

// A PriorityQueue implements heap.Interface and holds Items.
type PriorityQueue []*Item

func (pq PriorityQueue) Len() int { return len(pq) }

func (pq PriorityQueue) Less(i, j int) bool {
    // We want Pop to give us the highest, not lowest, priority so we use greater than here.
    return pq[i].priority > pq[j].priority
}

func (pq PriorityQueue) Swap(i, j int) {
    pq[i], pq[j] = pq[j], pq[i]
    pq[i].index = i
    pq[j].index = j
}

func (pq *PriorityQueue) Push(x interface{}) {
    n := len(*pq)
    item := x.(*Item)
    item.index = n
    *pq = append(*pq, item)
}

func (pq *PriorityQueue) Pop() interface{} {
    old := *pq
    n := len(old)
    item := old[n-1]
    item.index = -1 // for safety
    *pq = old[0 : n-1]
    return item
}

// update modifies the priority and value of an Item in the queue.
func (pq *PriorityQueue) update(item *Item, value string, priority int) {
    item.value = value
    item.priority = priority
    heap.Fix(pq, item.index)
}
Run Code Online (Sandbox Code Playgroud)

因此,确实,正如常见问题解答所说,要确定是否使用指针方法,请按顺序考虑以下因素:

  1. 该方法是否需要修改接收者?如果是,请使用指针。如果没有,应该有一个充分的理由,否则会造成混乱。
  2. 效率。如果接收器很大,例如一个大结构体,那么使用指针接收器会便宜得多。然而,效率并不容易讨论。如果您认为这是一个问题,请先对其进行分析和/或基准测试,然后再这样做。
  3. 一致性。如果该类型的某些方法必须具有指针接收器,则其余方法也应该具有指针接收器,因此无论如何使用该类型,方法集都是一致的。对我来说,这意味着如果该类型要用作指针(例如,频繁修改),则应该使用设置的方法来标记。这并不意味着一种类型只能有单独的指针方法,或者反之亦然。