【问题标题】:Adding a func never called improves behavior?添加一个从未调用过的函数可以改善行为?
【发布时间】:2012-06-14 19:00:46
【问题描述】:

下面的代码会产生不受欢迎的

[20010101 20010102].

取消注释 String 函数时,它会产生更好的效果(但不是我的实现):

[{20010101 1.5} {20010102 2.5}]

但是,永远不会调用 String func。 我看到 DateValue 中的 Date 是匿名的,因此 DateValue 正在使用 func (Date) String。

所以我的问题是:

1) 这是语言问题,fmt.Println 实现问题,还是 别的东西?注意:如果我切换自:

func (*DateValue) String() string

到

func (DateValue) String() string

我的函数至少被调用并且恐慌随之而来。因此,如果我真的想要调用我的方法,我可以这样做,但假设 DateValue 确实是一个非常大的对象,我只想通过引用传递。

2) 将匿名字段与 使用反射的 Stringer 和 json 编码等功能 在幕后?例如添加一个 String 或 MarshalJSON 方法 对于碰巧用作匿名字段的类型可能会导致 奇怪的行为(就像你只打印或编码整体的一部分)。

package main

import (
    "fmt"
    "time"
)

type Date int64

func (d Date) String() string {
    t := time.Unix(int64(d),0).UTC()
    return fmt.Sprintf("%04d%02d%02d", t.Year(), int(t.Month()), t.Day())
}

type DateValue struct {
    Date 
    Value float64
}

type OrderedValues []DateValue

/*
// ADD THIS BACK and note that this is never called but both pieces of
// DateValue are printed, whereas, without this only the date is printed
func (dv *DateValue) String() string {
    panic("Oops")
    return fmt.Sprintf("DV(%s,%f)", dv.Date, dv.Value )
}
*/

func main() {
    d1, d2 := Date(978307200),Date(978307200+24*60*60)
    ov1 := OrderedValues{{ d1, 1.5 }, { d2, 2.5 }}
    fmt.Println(ov1)
}

【问题讨论】:

    标签: go


    【解决方案1】:

    这是因为您传入了一个 DateValues 片段,而不是 DateValue 指针。既然您已经为*DataValue 定义了String 方法,那么*DateValue 就是实现Stringer 接口的方法。这也阻止了 DateValue 通过其匿名 Date 成员实现 Stringer 接口,因为只能使用值类型 (DateValue) 或指针类型 (*DateValue) 中的一种来实现接口。因此,当 fmt.Println 打印切片的内容时,它会看到元素不是 Stringers,并使用默认的结构格式而不是您定义的方法,给出[{20010101 1.5} {20010102 2.5}]。

    您可以将 OrderedValues 设为 []*DateValue 或定义 func (dv DateValue) String() string 而不是指针版本。

    【讨论】:

    • 谢谢。有用的点 - 想要更多关于(2)。关于 (1) 我不想使用 (*T) 表示性能。当 T 与 *T 的选择不符合条件时,为什么客户选择 []T 与 []*T 会改变行为fmt.Println()。也就是说 fmt.Println(DateValue{ Date(978307200), 1}) 与 fmt.Println(&DateValue{ Date(978307200), 1}) 都可以按需要工作。因此,具有所有反射能力的 c(sh) 不会 Println 对切片执行所需的操作。除此之外,我现在害怕在匿名的情况下抛出 String/MarshalJSON,因为害怕失去现有的功能(如 c++ 切片)。
    • @user1338952 我没有看到这两个 Println 调用都按预期工作:play.golang.org/p/Pz7J581xnl。如果我删除 func (d *DateValue) String() 方法,它们都只打印日期,如果我将其更改为 func (d DateValue) String(),它们都会打印我们想要的东西(这是我所期望的)。
    • 你是对的。我的观点是 print([]T) 与 print([]*T) 不同,但 print(T{}) 与 print(&T{}) 相同。我觉得我应该选择接收器 (d*DateValue) 或 (d DateValue),它不会影响结果。我也认为 fmt 没有做正确的事情是一个问题。在以下示例中,我模拟了我认为 println c(sh) 应该在这里做的事情(非常可怜的人的概念证明):play.golang.org/p/i_lxXAqlKC
    【解决方案2】:

    根据@SteveM 所说,我将其提炼成一个更简单的测试用例:

    package main
    
    import "fmt"
    
    type Fooable interface {
      Foo()
    }
    
    type A int
    
    func (a A) Foo() { }
    
    type B struct {
      A
    }
    
    // Uncomment the following method and it will print false
    //func (b *B) Foo() { }
    
    func main() {
      var x interface{} = B{}
      _, ok := x.(Fooable)
      fmt.Println(ok) // prints true
    }
    

    换句话说,当为*B 定义了Foo 方法时,Foo 方法不是B 方法集的一部分。

    通过阅读规范,我没有看到对正在发生的事情的明确解释。最接近的部分似乎在selectors 部分:

    对于 T 或 *T 类型的值 x,其中 T 不是接口类型,x.f 表示 T 中最浅深度的场或方法 是这样的f。

    我可以看到这个解释正在发生的事情的唯一方法是,如果它在 B 的最浅深度寻找方法 Foo,它也会考虑到 *B 的方法,出于某种原因(即使我们正在考虑输入B 而不是*B);而*B 中的Foo 确实比A 中的Foo 浅,所以它把那个作为候选人;然后它发现Foo 不起作用,因为它在*B 而不是B,所以它完全摆脱了Foo(即使有一个有效的继承自A)。

    如果这确实是正在发生的事情,那么我同意 OP,因为这是非常违反直觉的 添加 一个方法到 *B 会产生相反的结果 从B 中删除一个方法。

    也许更熟悉 Go 的人可以澄清这一点。

    【讨论】:

    • 当你显式定义 Foo 时,隐式定义就消失了。这只是无法在指针和值类型上定义相同方法的结果:play.golang.org/p/SpkghJdUEl
    • @SteveM:但它在规范中的什么地方这么说?
    • "任何其他类型 T 的方法集由所有接收者类型为 T 的方法组成。对应指针类型 *T 的方法集是所有接收者为 *T 或 T 的方法集(即是,它还包含 T) 的方法集。……在一个方法集中,每个方法必须有一个唯一的方法名称。”所以,如果我在 T 上有一个方法,我也不能在 *T 上定义一个同名方法。 golang.org/ref/spec#Method_sets
    • @SteveM: 但这并不是说如果我在 *T 上有一个方法,我就不能在 T 上有相同的方法
    • 我不确定这是否符合规范的精神,但您总是会收到 *T 方法集的错误,所以没关系。
    猜你喜欢
    • 1970-01-01
    • 2014-10-21
    • 2012-10-26
    • 2021-07-14
    • 2016-05-23
    • 2020-11-25
    • 2010-10-10
    • 1970-01-01
    • 2015-12-22
    相关资源
    最近更新 更多