通过引用传递自定义切片类型

时间:2013-09-11 01:39:03

标签: pointers go slice

我无法绕过Go中指针,切片和界面的交互方式。这就是我目前编码的内容:

type Loader interface {
  Load(string, string)
}

type Foo struct {
  a, b string
}

type FooList []Foo

func (l FooList) Load(a, b string) {
  l = append(l, Foo{a, b})
  // l contains 1 Foo here
}

func Load(list Loader) {
  list.Load("1", "2")
  // list is still nil here
}

鉴于此设置,我尝试执行以下操作:

var list FooList
Load(list)
fmt.Println(list)

但是,列表始终为nil。我的FooList.Load函数确实向l切片添加了一个元素,但这就是它所获得的。加载中的list仍为nil。我想我应该能够将引用传递给我的切片并将其附加到它上面。我显然错过了如何让它工作的东西。

4 个答案:

答案 0 :(得分:2)

http://play.golang.org/p/uuRKjtxs9D中的代码)

如果您打算进行更改,可能需要使用指针接收器。

// We also define a method Load on a FooList pointer receiver.
func (l *FooList) Load(a, b string) {
    *l = append(*l, Foo{a, b})
}

但结果是,FooList值本身不会满足Loader接口。

var list FooList
Load(list)      // You should see a compiler error at this point.

指针到FooList值,将满足Loader接口。

var list FooList
Load(&list)

完整的代码如下:

package main

import "fmt"

/////////////////////////////
type Loader interface {
  Load(string, string)
}

func Load(list Loader) {
    list.Load("1", "2")
}
/////////////////////////////


type Foo struct {
  a, b string
}

// We define a FooList to be a slice of Foo.
type FooList []Foo

// We also define a method Load on a FooList pointer receiver.
func (l *FooList) Load(a, b string) {
    *l = append(*l, Foo{a, b})
}

// Given that we've defined the method with a pointer receiver, then a plain
// old FooList won't satisfy the Loader interface... but a FooList pointer will.

func main() {
    var list FooList
    Load(&list)
    fmt.Println(list)
}

答案 1 :(得分:1)

我将简化问题,以便更容易理解。正在做什么与此非常相似,也不起作用(你可以运行它here):

type myInt int

func (a myInt) increment() { a = a + 1 }
func increment(b myInt)    { b.increment() }

func main() {
    var c myInt = 42
    increment(c)
    fmt.Println(c) // => 42
}

这不起作用的原因是因为Go按值传递参数,因为documentation describes

  

在函数调用中,通常会计算函数值和参数   订购。在评估它们之后,调用的参数将按值传递   函数和被调用函数开始执行。

实际上,这意味着上面示例中的abc每个都指向不同的int变量,a和{{1作为初始b值的副本。

要修复它,我们必须使用指针,以便我们可以引用相同的内存区域(runnable here):

c

现在type myInt int func (a *myInt) increment() { *a = *a + 1 } func increment(b *myInt) { b.increment() } func main() { var c myInt = 42 increment(&c) fmt.Println(c) // => 43 } a都是包含变量b地址的指针,允许它们各自的逻辑更改原始值。请注意,此处记录的行为仍然存在:ca仍然是原始值的副本,但作为<{1}}函数的参数提供的原始值是地址 b

切片的情况与此没有什么不同。它们是引用,但引用本身是作为参数按值提供的,因此如果更改引用,则调用站点将不会观察到更改,因为它们是不同的变量。

但是,还有一种不同的方式可以使其工作:实现类似于标准increment功能的API。再次使用更简单的示例,我们可以在不改变原始值的情况下实现c,而不使用指针,而是返回更改的值:

append

您可以在标准库中的许多位置看到该技术,例如strconv.AppendInt函数。

答案 2 :(得分:0)

值得保持Go的数据结构如何实现的心理模型。这通常可以更容易推理出这样的行为。

http://research.swtch.com/godata是对高级视图的一个很好的介绍。

答案 3 :(得分:0)

Go是按值传递。对于参数和接收器都是如此。如果需要分配切片值,则需要使用指针。

  

然后我在某处读到你不应该将指针传递给切片   它们已经是参考文献

这不完全正确,并且缺少故事的一部分。

当我们说某些东西是“引用类型”时,包括地图类型,通道类型等,我们的意思是它实际上是指向内部数据结构的指针。例如,您可以将地图类型视为基本定义为:

// pseudocode
type map *SomeInternalMapStructure

因此,要修改关联数组的“内容”,您不需要分配给地图变量;您可以按值传递map变量,该函数可以更改map变量指向的关联数组的内容,并且调用者可以看到它。当您意识到它是指向某些内部数据结构的指针时,这是有意义的。如果要更改要指向的内部关联数组,则只能分配给地图变量。

然而,切片更复杂。它是一个指针(对于一个内部数组),加上的长度和容量,两个整数。所以基本上,你可以把它想象成:

// pseudocode
type slice struct {
    underlyingArray uintptr
    length int
    capacity int
}

所以它不仅仅是一个指针。它是一个关于底层数组的指针。但是长度和容量是切片类型的“值”部分。

因此,如果您只需要更改切片的元素,那么是的,它就像一个引用类型,因为您可以按值传递切片并让函数更改元素,并且它对调用者可见。< / p>

然而,当你append()(这就是你在问题中所做的)时,情况会有所不同。首先,追加影响切片的长度,长度是切片的直接部分之一,而不是指针后面。其次,追加可能会产生不同的底层数组(如果原始底层数组的容量不够,则分配一个新的)。因此,也可以改变切片的数组指针部分。因此,有必要改变切片值。 (这就是append()返回某些东西的原因。)从这个意义上讲,它不能被视为一种参考类型,因为我们不只是“改变它指向的东西”;我们正在直接改变切片。