2013-09-11 36 views
0

我在围绕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。我想我应该能够通过我的切片的参考,并附加到它的东西。我明显错过了如何让它工作。

回答

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) 
} 
+0

我发誓,这是我开始,但得到类型错误。然后,我读了一些地方,你不应该把指针指向片,因为它们已经是引用,这就是我感到困惑的地方......无论如何,现在它都可以工作。谢谢! – Bill

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 
} 

之所以这样行不通是因为围棋按值传递参数,为documentation describes

在函数调用中,函数值和参数按通常的 顺序进行评估。评估完成后,调用的参数将通过值 传递给函数,并且调用的函数开始执行。

在实践中,这意味着每个的abc在上面的例子中都指向不同INT变量,与ab为初始值c的副本。

为了解决这个问题,我们必须使用指针,这样我们可以参照相同的内存区域(可运行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) // => 43 
} 

现在ab是包含变量的地址两个指针c,允许它们各自的逻辑改变原始值。请注意,记录的行为在此仍然存在:ab仍为原始值的副本,但作为参数提供给increment函数的原始值是地址c

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

但是,还有一种不同的方式可以使它工作:实现类似于标准功能的API。再次使用简单的例子,我们有可能实现的increment没有变异的原始值,并且不使用指针,由而不是返回更改后的值:

func increment(i int) int { return i+1 } 

你可以看到在标准在许多地方使用这种技术库,例如strconv.AppendInt函数。

0

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

然后我读的地方,你不应该因为 传递指针到切片他们已经引用

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

当我们说什么是“引用类型”,包括地图类型,通道类型等时,我们的意思是它实际上是一个指向内部数据结构的指针。例如,你可以把地图类型的基本定义为:

// pseudocode 
type map *SomeInternalMapStructure 

所以修改关联数组的“内容”,你并不需要分配给地图变量;您可以按值传递一个map变量,该函数可以更改map变量指向的关联数组的内容,并且它将对调用者可见。当你意识到它是指向某些内部数据结构的指针时,这是有道理的。如果您想更改哪个内部关联数组您希望它指向您将只分配给地图变量。

但是,切片更复杂。它是一个指针(指向一个内部数组),加上的长度和容量,两个整数。所以基本上,你可以这样想:

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

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

所以,如果你只需要改变切片的一个元素,那么是的,它就像一个引用类型,因为你可以通过值传递切片并让该函数改变一个元素,并且它对调用者可见。

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