2014-11-01 39 views
4

我很難理解如何設置已經作爲指針傳遞的接口值。我試圖完成沿此線的東西:設置接口引用{}參數

import "fmt" 

var Stuff map[string]interface{} 

func main() { 
    var num int 
    Stuff["key"] = 9001 
    get("key", &num) 
    fmt.Println("num:", num) 
} 

func get(k string, v interface{}) { 
    *v = Stuff[k] 
} 

什麼會我必須做我的程序輸出是

num: 9001 

編輯:有沒有使用reflect可能包羅萬象的解決方案?

+1

只是猜測,但也許你正試圖從appengine數據存儲'Get'。通過指針(GetMulti,指向片的指針)返回是奇怪的鴨子;它似乎也許是他們試圖在實現中保持混亂,而不是返回一個'interface {}'並讓調用者轉換。要做這樣的事情,你將不得不使用'反射',這是沒有趣。如果你想處理特定的切片類型(專用或作爲幾種類型的「快速路徑」),請使用類似於PeterSO所說的類型斷言(或幾種類型的開關)。 – twotwotwo 2014-11-02 06:32:58

+0

你可能看過一篇名爲「反思法則」的博客文章。第一條反思原則應該避免反思。 :)但是,無論在這裏還是在任何地方,您的代碼都需要閱讀或構建任意類型的結構。 – twotwotwo 2014-11-02 06:42:16

+1

不錯的一個。它是appengine數據存儲區。我完全應該這樣說,但我正在編寫一個測試實現,它基本上是數據存儲的緊湊包裝。 – BlinkyTop 2014-11-02 15:49:02

回答

6

可以效仿使用reflect的應用服務引擎的數據存儲接口;通常我會說盡量減少反射,但是你(和AppEngine和其他ORMs)在這裏沒有其他很好的選擇來呈現你想要的接口。對於一些模擬Get你:

  • 得到reflect.ValueValueOf()
  • 得到您想要創建的東西類型
  • reflect.Zero
  • 創建它在一些數據可選填充reflect.Field()等。
  • 使用reflect.Indirect()Value.Set()通過指針設置原件。

,僅僅通過指針歸零一個struct一個簡單的例子是在http://play.golang.org/p/g7dNlrG_vr和複製在這裏:

package main 

import (
    "fmt" 
    "reflect" 
) 

func main() { 
    i := 1 
    clear(&i) 
    fmt.Println(i) 
} 

func clear(dst interface{}) { 
    // ValueOf to enter reflect-land 
    dstPtrValue := reflect.ValueOf(dst) 
    // need the type to create a value 
    dstPtrType := dstPtrValue.Type() 
    // *T -> T, crashes if not a ptr 
    dstType := dstPtrType.Elem() 
    // the *dst in *dst = zero 
    dstValue := reflect.Indirect(dstPtrValue) 
    // the zero in *dst = zero 
    zeroValue := reflect.Zero(dstType) 
    // the = in *dst = 0 
    dstValue.Set(zeroValue) 
} 

對於模擬GetMulti你需要更多的步驟,切片工作。一個例子是在http://play.golang.org/p/G_6jit2t-2及以下:

package main 

import (
    "fmt" 
    "reflect" 
) 

func main() { 
    s := []int{} 
    getMultiZeroes(&s, 10) 
    fmt.Println(s) 
} 

func getMultiZeroes(slicePtrIface interface{}, howMany int) { 
    // enter `reflect`-land 
    slicePtrValue := reflect.ValueOf(slicePtrIface) 
    // get the type 
    slicePtrType := slicePtrValue.Type() 
    // navigate from `*[]T` to `T` 
    sliceElemType := slicePtrType.Elem().Elem() // crashes if input type not `*[]T` 
    // we'll need this to Append() to 
    sliceValue := reflect.Indirect(slicePtrValue) 
    // and this to Append() 
    sliceElemValue := reflect.Zero(sliceElemType) 

    // append requested number of zeroes 
    for i := 0; i < howMany; i++ { 
     // s := append(s, v) 
     sliceValue.Set(reflect.Append(sliceValue, sliceElemValue)) 
    } 
} 

在現場代碼(而不是測試像你這樣做),這將會是更快地使用一種類型的開關(如馬丁建議),因此專門的本地代碼針對每種類型運行;如果按類型有不同的行爲,這也可能很方便。爲GetMulti一個例子是在http://play.golang.org/p/q-9WyUqv6P及以下:

package main 

import "fmt" 

func main() { 
    s := []int{} 
    getZeroes(&s) 
    fmt.Println(s) 

    fails := []float32{} 
    getZeroes(&fails) 
} 

func getZeroes(slicePtrIface interface{}) { 
    switch sp := slicePtrIface.(type) { 
    case *[]int: 
     (*sp) = append((*sp), 0, 0) 
    case *[]string: 
     (*sp) = append((*sp), "", "") 
    default: 
     panic(fmt.Sprintf("getZeroes: passed type %T, which is not a pointer to a slice of a supported type", slicePtrIface)) 
    } 
} 

你甚至可以平凡二者結合起來;編寫常見類型的自定義代碼,並在默認情況下調用基於緩慢的reflect版本。在http://play.golang.org/p/6qw52B7eC3演示(不復制,因爲它是一個這樣簡單的縫合在一起的上述兩個)。

碰巧有another recent question關於如何將值傳遞給GetMulti,而不是模擬GetMulti本身,如果出現。


更一般參考,而不是回答這個問題:

「圍棋缺乏按引用傳遞」是需要了解的,還需要一些闡述。 Go有指針和其他類型的切片,包含指向數據的指針。沒有「通過引用傳遞」的意思就是Go永遠不會將一個值參數(int,struct)隱式地轉換爲指針。 C++引用參數的確如此:C++ void f(i int&) { i++; }在調用方中更改了i,而調用方沒有明確地在調用點處傳遞指針。 func (i int) { i++ }沒有。

在Go中,您可以查看傳遞給函數調用的類型並告訴它可以更改哪些數據。使用C++引用參數或某些語言的「通過引用傳遞」語義,任何調用都可能會改變本地語言;沒有查看聲明就無法說清楚。爲了避免不必要的數據複製,在slice,string,map,interface和channel values的實現中已經有了指針。在這些類型中,指針,切片和地圖實際上會讓您通過它們修改數據。此外,與在C++中一樣,Go的this類接收器參數可以是調用代碼中沒有明確的&的指針。有更多關於這個在Russ Cox's godata postthis summary on when you need a pointer or not

+0

剛剛意識到您可能正在執行'Get',並且這裏只討論了'GetMulti',所以我添加了一個沒有額外片段相關位的示例。 – twotwotwo 2014-11-09 05:56:03

1

首先:有絕對在Go中沒有「通過引用」的概念。沒有。你什麼可以是繞過一個指針。這個指針值是按值傳遞的,因爲Go中的所有東西都是按值傳遞的。第二:不要傳入一個指向接口的指針,並修改pointees的值(可行但醜陋),你可以返回值(更好)。

第三:它不能(即不是沒有反射或不安全)沒有類型斷言。 而且你永遠不應該(直到你掌握Go和接口爲止),使用指向接口的指針。

第五:如果您的解決方案需要interface{}您可能會做錯事。你確定你的實體不能被一些(非空的)接口描述嗎?

這就是說,這樣的工作。

func main() { 
    var num int 
    Stuff["key"] = 9001 
    num = get("key").(int) 
} 
func get(k string) interface{}{ 
    return Stuff[k] 
} 
+1

我寧願從函數返回值,但我試圖滿足一個已經標準化的接口(根據應用引擎數據存儲的具體情況而定) – BlinkyTop 2014-11-01 12:27:48

1

Martin Gallagher解決方案非常完美,但正如他所說的,您不能在Golang中使用泛型,因此代碼看起來有點難看。我想另一種解決方案是始終使用interface{}作爲類型,然後在程序中投射(或檢查類型)。事情是這樣的:http://play.golang.org/p/0o20jToXHV

2

The Go Programming Language Specification

Calls

在函數調用時,函數值和變量 通常的順序進行評估。評估完成後,呼叫 的參數將按值傳遞給函數,被調用的函數將開始執行 。當函數返回時,函數的返回參數通過值 傳回給調用函數。

在去一切都通過價值;沒有任何東西通過參考傳遞因此,傳遞一個指針。例如,

package main 

import "fmt" 

var Stuff map[string]interface{} 

func main() { 
    Stuff = make(map[string]interface{}) 
    Stuff["key"] = 9001 
    var value interface{} 
    get("key", &value) 
    num := value.(int) 
    fmt.Println("num:", num) 
} 

func get(k string, v interface{}) { 
    *v.(*interface{}) = Stuff[k] 
} 

輸出:

 
num: 9001