2013-09-11 35 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()會返回一些內容。)從這個意義上講,它不能被視爲一種參考類型,因爲我們不僅僅是「改變它指向的內容」。我們正在直接改變切片。