为什么存储在接口中的值在Golang中无法寻址

时间:2018-02-14 15:25:10

标签: go

引用golang wiki(https://github.com/golang/go/wiki/MethodSets#interfaces):

"存储在界面中的具体值不可寻址,就像地图元素不可寻址一样。"

这里解释了地图值不可寻址的问题: Why are map values not addressable?

然而,关于界面还不清楚。为什么他们无法解决?这是因为一些硬设计假设吗?

4 个答案:

答案 0 :(得分:14)

为什么接口中存储的非指针值不可寻址?这是一个很好的问题,答案解释了为什么包含非​​指针值的接口不能成为带指针接收器的方法的接收器,导致可怕的错误:

<type> does not implement <interface> (<name> method has pointer receiver)

<强> TL;博士

存储在接口中的非指针值不可寻址以维护类型完整性。例如,指向 A 的指针,指向接口中类型 A 的值,随后在接口中存储不同类型 B 的值时无效。

由于存储在接口中的非指针值不可寻址,因此编译器无法将其地址传递给带有指针接收器的方法。

答案很长

我在网上看到的答案没有多大意义。例如,this article说:

  

原因是界面中的值位于隐藏内存中   位置,等编译器无法自动获取指针   那个记忆对你来说(在Go的说法中,这被称为“不是   寻址”)。

存储在接口中的值确实是不可寻址的,但据我所知,并不是因为它存储在“隐藏的存储位置”中。

另一个常见的答案是:

  

创建接口值时,将复制包含在接口中的值。因此无法获取其地址,即使您这样做,使用指向接口值的指针也会产生意外影响(即无法更改原始复制值)。

这没有任何意义,因为指向复制到接口中的值的指针与指向复制到具体类型的值的指针没有什么不同;在这两种情况下,您都无法通过指向副本的指针更改原始复制值。

那么为什么存储在接口中的值不可寻址?如果 可寻址,那么答案就在于后续影响。

假设您有一个界面, ,还有两种类型, A < em> B ,满足该界面:

type I interface{}
type A int
type B string

创建 A 并将其存储在 I 中:

func main() {
    var a A = 5
    var i I = a
    fmt.Printf("i is of type %T\n", i)

让我们假装我们可以获取存储在界面中的值的地址:

    var aPtr *A
    aPtr = &(i.(A)) // not allowed, but if it were...

现在创建 B 并将其存储在 i 中:

    var b B = "hello"
    i = b
    fmt.Printf("i is of type %T, aPtr is of type %T\n", i, aPtr)
}

这是输出:

i is of type main.A
i is of type main.B, aPtr is of type *main.A

B 放入 i 之后,什么是 aPtr 指着? aPtr 被声明为指向 A ,但 t < / strong>现在包含 B aPtr 不再是指向 A

但是允许这样做:

    var aPtr *A
    var a2 A = i.(A)
    aPtr = &a2

因为第二行在 i。(A) aPtr 中复制了该值不指向 i。(A)

那么,为什么包含非​​指针值的接口不能成为带指针接收器的方法的接收器?由于存储在接口中的非指针值不可寻址,因此编译器无法将其地址传递给带有指针接收器的方法。

答案 1 :(得分:1)

我认为答案是&#34;因为Go没有引用&#34;。如果您致电Foo(x),您知道x无法修改;如果你打电话给Foo(&x),你可能会想到它。如果您要求的是可能的(特别是,如果您希望值本身被解决,而不是在界面中制作的副本),那么这将会破坏:

func Bar() {
    b := 42
    Foo(b)
    fmt.Println(b)
}

func Foo(v interface{}) {
    // Making up syntax now
    p = takeAddressAs(v).(*int)
    *p = 23
}

请注意, 可能(从技术角度来看)到address存储在界面中的副本,它绝对可以构建一种允许它的语言修改原始值(就像你似乎想要的那样):这基本上就是Python的工作原理。当v = x是接口类型时,您可以使v = &x成为v的语法糖。但是这会将参考值添加到Go,它故意缺乏。

我认为这里混淆的根本原因是Go有句法糖来调用b.Foo(),即使Foo有一个指针接收器(只要b是可寻址的)。您可以打电话给b.Write()但您无法fmt.Fprintln(b, 42),这是有道理的。可以说,Go应该没有那个糖,而是要求你明确地做(&b).Write或者只是让它成为开头的指针(使用,例如b := new(bytes.Buffer)而不是var b bytes.Buffer)。但答案是:a)它非常方便b)它似乎 意外,b.Foo()可能会修改b。< / p>

tl; dr 是:a)Go没有引用,b)没有技术原因它没有它们(与其他答案相反)但是c)决定不要他们。

答案 2 :(得分:0)

Go中的接口是类型必须提供的方法的定义,如果要将其用作接口的类型。

使用接口的代码不关心实现类型如何做到这一点,它只关心接口方法是否满足。

举个例子,假设我有一个代码使用的简单记录器接口:

type Logger interface {
    Printf(format string, args ...interface{})
    Errorf(format string, args ...interface{})
}

我可以通过我的代码使用此界面。我需要在需要的地方提供Logger的实现。可能有几种实现方式。我可能有FileLogger写入文件。我的代码是否应该能够访问File中的FileLogger属性?

我可能有ConsoleLoggerSockerLogger以及LogglyLogger,所有这些都满足界面。但是,即使拥有File属性也没有任何意义。如果我使用Logger接口的代码对底层File感兴趣,那么在使用其他Logger实现时它会做什么?

如果需要访问接口实现中的基础数据,则可能使用的接口不正确。

解决这个问题的方法可能是在界面上使用Getter / Setter?如果您确实需要从底层结构访问值,请创建该接口的那一部分。在上面的示例中,GetFile()没有多大意义,但也许还有其他情况,其中接口的所有实现共享公共属性,例如GetLen()SetLen()

答案 3 :(得分:0)

转到文档中的有用信息:https://golang.org/doc/faq#different_method_sets

即使在编译器可以将值的地址用于 传递给该方法,如果该方法修改了值,则更改将 在呼叫者中迷路。例如,如果 bytes.Buffer使用值接收器而不是指针,此代码:

import random


class cell:
    """ cell for celluar automata """
    cells = []

    def __init__(self, n=0, nghbrs=[], a=0.00, b=0.00, c=0.00):
        self.n = n #id
        self.nghbrs = nghbrs #list of connected neighbors
        self.a = a  #a value of the cell 
        self.b = b
        self.c = c

    def growUp(self):
        if self.a > .7:  # if cell is "bright"
            self.cells[self.nghbrs[7]].a = self.a  # update cell above (nghbrs[7] = cell above )


def main():
    iterations = 4
    links = initLinks()  # 150 random links [link0, link2, ... , link7]*150
    val1 = initval()  # 150 random values

    cell.cells = [cell(nghbrs[0], nghbrs[1], val1[nghbrs[0]])for nghbrs in enumerate(
        links)]  # create cell objects, store them in cells and init. neigbours , a

    for i in range(iterations):  # celluar automata loop
        for c in cell.cells:
            c.growUp()


def initLinks(): #for stackoverflow; in real use the cells are arranged in a grid
    nghbrs = []
    for i in range(150):
        links = []
        for j in range(8):
            links.append(random.randrange(0, 150))
        nghbrs.append(links)
    return nghbrs


def initval():
    vals = []
    for i in range(150):
        vals.append(random.random())
    return vals


if __name__ == "__main__":
    main()

会将标准输入复制到buf的副本中,而不是复制到buf本身。这几乎永远不是想要的行为。