【问题标题】:Pointer Receiver and Value Receiver on Interfaces in GolangGolang 接口上的指针接收器和值接收器
【发布时间】:2018-12-10 07:50:46
【问题描述】:

这个问题没有我想的那么清楚,我会问一个更好的问题。但我不想在上面标记重复。所以我已经标记了我自己的问题。如果您可以帮助将其删除,以免混淆社区。请只做那些需要的。当你在它的时候,请不要对我投反对票。抱歉不清楚

我是 golang 的新手,刚刚掌握了窍门。

我通过 Tour of Go 学习,然后根据自己的理解使用它。 我在Interfaces 并开始以我自己的理解来实施。 这里是Go PlayGround Link

第 1 步:我制作了 3 种类型,一个 int、一个结构和一个接口

package main

import (
    "fmt"
)

type MyInt int

type Pair struct {
    n1, n2 int
}

type twoTimeable interface {
    result() (int, int)
}

第 2 步:然后我为指针接收器实现了 twoTimeable,因为它改变了基础值。

func (p *Pair) result() (int, int) {
    p.n1 = 2 * p.n1
    p.n2 = 2 * p.n2
    return p.n1, p.n2
}

func (m *MyInt) result() (int, int) {
    *m = 2 * (*m)
    return int(*m), 0
}

第 3 步:然后我在 main 函数中声明并分配了 MyInt、Pair 及其对应的指针。我还声明了 twoTimeable 接口

mtemp := MyInt(2)
var m1 MyInt
var m2 *MyInt
var p1 Pair
var p2 *Pair
m1, m2, p1, p2 = MyInt(1), &mtemp, Pair{3, 4}, &Pair{5, 6}
var tt twoTimeable

fmt.Println(" Values  : ", m1, *m2, p1, *p2, tt)

第 4 步:我分配了 MyInt,Pair 及其指针,调用实现的方法并打印。

tt = m1
fmt.Println(tt.result())
fmt.Printf("Value of m1 %v\n", m1)

tt = m2
fmt.Println(tt.result())
fmt.Printf("Value of m2 %v\n", *m2)

tt = p1
fmt.Println(tt.result())
fmt.Printf("Value of p1 %v\n", p1)

tt = p2
fmt.Println(tt.result())
fmt.Printf("Value of p2 %v\n", *p2)

显示错误:

prog.go:41:5: cannot use m1 (type MyInt) as type twoTimeable in assignment:
    MyInt does not implement twoTimeable (result method has pointer receiver)
prog.go:49:5: cannot use p1 (type Pair) as type twoTimeable in assignment:
    Pair does not implement twoTimeable (result method has pointer receiver)

我还阅读了this question,我知道 m1 和 p1 不可寻址,这就是它无法编译的原因。 但是如果我使用 p1 或 m1 p1/m1.result()(自动取消引用 FTW),方法 result() 就可以正常工作

现在在第 2 步中,我将指针接收器更改为值接收器,并将 *m 更改为 m(我知道输出发生了变化)

func (p Pair) result() (int, int) {
    p.n1 = 2 * p.n1
    p.n2 = 2 * p.n2
    return p.n1, p.n2
}

func (m MyInt) result() (int, int) {
    m = 2 * (m)
    return int(m), 0
}

它突然不显示编译错误。对于 m2 和 p2 不应该这样,因为现在没有使用 *MyInt 和 *Pair 实现结果

这意味着如果接口方法实现有值接收者 tt 可以同时保存指针和值。 但是当接口方法实现有指针接收器时,它不能保存非指针。

Q1:为什么在值接收器接口方法(p MyInt)结果()上使用指针(tt = m2)很好,但在指针接收器接口方法(p *MyInt)上使用值(tt = m1)结果( )。

现在让我们回到指针接收器。

如果函数接受参数(tt twoTimeable)我将能够调用 tt.result(),而不管 tt 是否是指向类型的指针,是否有一种方法,给定的 result() 仅由指针接收器定义。

请看下面的代码:

func doSomething(tt twoTimeable) {
    temp1, temp2 := tt.result()
    fmt.Print("doing something with ", temp1, temp2)
} 

由于 tt 可以是任何预定义类型(指针或非指针),接受参数(tt *twoTimeable,如果我能做到的话)似乎不是一个解决方案,或者我是否依赖函数用户来提供指针.如果我不必更改基础值,即使用值接收器,这不是问题,因为 tt 可以保存值或指向该值的指针

我总是接受答案。

【问题讨论】:

  • 请查看play.golang.org/p/fvFvsONstaK 以了解我使用值接收器并且它没有显示编译错误的其他情况
  • 您的 Playground 代码显示了对值接收器(此处为 Pair)的常见误解:在底层 Pair 的 copy 上调用方法 Pair.result。此副本在第 19 行和第 20 行被修改。方法完成后,修改后的副本不再存在。这_与接口无关。
  • 问题被误解了,我会在以后的问题中更清楚。但是现在 如果接口 twoTimeable 是用 (m *MyInt) 实现的,那么可以为 twoTimeable 类型的变量分配一个 *MyInt 类型的变量。但是如果你改变实现,使得接收者变成 (m MyInt),一个 twoTimeable 类型的变量可以保存 MyInt 和 *MyInt。为什么?
  • 如果你真的感兴趣:想出一个小例子,用合理和简短的名字来显示所有 one例子中的行为。这种“如果你改变实现”只会让讨论变得复杂。
  • 您可能会将您的问题分成 2 或 3 个不同的问题,1 个问题似乎是为什么 play.golang.org/p/rIQnPjrhLPL(您的代码的简化部分)会导致错误。另一个问题可能是为什么将 result() 方法更改为采用非指针 func (m MyInt) result() (int, int) { 不会更改 MyInt 的值。另一个问题可能是“如何只创建一个结果方法,当分配给twoTimeable 接口时,它适用于指针和非指针版本。保持你的问题简短而清晰。

标签: go


【解决方案1】:

Q1:为什么在值接收器方法上使用指针可以,反之则不行?

因为语言规范说得很好:https://golang.org/ref/spec#Method_sets

对应指针类型*T的方法集是所有以接收者*T或T声明的方法的集合(即它也包含T的方法集)。

(规范是这样的一个可能原因:方便。)

对于剩下的问题:我完全不知道你在问什么。 do Something 的代码完全没问题。您在tt 上调用result,这是实现result() (int, int) 方法的任何类型。这种类型是否是指针类型并不重要。你从这个方法调用中得到两个ints,你可以用这些整数做任何你想做的事情。

(关于“指向接口的指针”部分:是的。在技术上可以做到ttp *twoTimetable。但你从不需要这个。从不。(当然这是一个谎言,但事实如此你需要它是如此罕见和精致,以至于你作为初学者真的不需要它,一旦你确实需要它,你就会知道如何使用它。)这里要记住一件事:你从不做“指向界面的指针”。)

【讨论】:

  • 但是 tt twoTimeable 不能容纳非指针,它显示编译错误。
  • 不,这个说法是错误的。 twoTimetables 可以 持有非指针。编译错误源自其他地方。事实是:twoTimetables 只能保存实现result() (int, int) 的类型。
  • 是的。那就是问题所在。 MyInt 不是 twoTimetable。只有 *MyInt 是。
  • 如果我让 MyInt twoTimeable 而没有 *MyInt ,为什么 *MyInt 会自动变成 twoTimeable
  • "为什么 *MyInt 会自动变成 twoTimeable" 它没有。它 *MyInt 有正确的方法。 MyInt 没有。这些事情不会“成为”两个时间表。他们要么有,要么没有正确的方法集。如果他们确实有正确的方法:他们可以被分配给一个 twoTimestable。有两种方法可以获得正确的方法。有关详细信息,请参阅链接语言规范。我敦促您阅读规范的链接部分。 (像 MyInt 和 twoTimestable 这样更好的名称也会有所帮助)。
【解决方案2】:

如果函数接受参数(tt twoTimeable)我将能够调用 tt.result(),而不管 tt 是否是指向类型的指针,是否有一种方法,给定的 result() 仅由指针接收器定义。

这似乎是一个重大的误解。如果tt 是接口类型twoTimetable 的变量,那么一个result 方法并且可以调用此方法。为什么tt 的基础类型是 does not matter。接口类型完全封装了相关的底层类型。一个接口类型提供了一组方法,这些方法可以被调用,而没有其他方法可以调用。

具体类型(例如您的 MyInt)对 twoInterfaces 类型的变量的可分配性问题是一个不同 问题。如果您的具体类型(例如 MyInt)恰好具有由接口类型(此处为 twoTimetables)定义的所有方法,那么它是可分配的,如果不是,则不是。

“我的具体类型有什么方法”这个问题是第三个问题。这是唯一一个有点复杂的问题。在接收器 T 上定义的任何方法都是 T 的方法。对于指针类型 *T 上的方法 *T 上 T 计数的方法。

【讨论】:

    【解决方案3】:

    不应该是 m2 和 p2,因为现在没有使用 *MyInt 和 *Pair 实现结果

    没有。你把不同的东西混为一谈,曲解了结果。

    Go 允许您在大多数情况下省略指针解引用。

    type Pair struct {
        n1, n2 int
    }
    var pp *Pair = ...
    fmt.Println(pp.n1)
    

    看到最后一行了吗? pp 是指向 Pair 的指针,但不需要写 (*pp).n1。 这种取消引用是自动插入的(它是 C 中 -> 运算符的自动变体)。

    您的 m2 和 p2 只是包含指针的变量。如果需要,这些指针将被取消引用。这与指针/值接收器或接口无关

    【讨论】:

      猜你喜欢
      • 2018-01-20
      • 1970-01-01
      • 2018-10-14
      • 2015-03-02
      • 1970-01-01
      • 1970-01-01
      • 2016-09-22
      • 2014-07-30
      相关资源
      最近更新 更多