2014-09-13 69 views
1

我想創建一個函數,它可以接受映射或任何其他數組,並遍歷它調用每個項目上的函數,該函數知道如何處理遇到的任何類型。遍歷接口

這是我第一次失敗的嘗試。目前,當我在我的真實使用案例中運行它時,它總是說「呃哦!」。

func DoTheThingToAllTheThings(data_interface interface{}) int { 
    var numThings int 

    switch data := data_interface.(type) { 
    case map[interface{}]interface{}: 
     numThings = len(data) 
     for index, item := range data { 
      DoTheThing(index, item) 
    } 
    case []interface{}: 
     numThings = len(data) 
     for index, item := range data { 
      DoTheThing(index, item) 
     } 
    default: 
     fmt.Println("uh oh!") 
    } 

    return numThings 
} 

數組或地圖可以包含很多不同的東西,所以它不是嘗試所有可能的輸入匹配的選項。

另有說明,有沒有辦法在Go中迭代數組或地圖而不知道它是什麼?

+1

下面的「基於反射」的解決方案將起作用,但是地道的東西只是在可能的情況下在callsite處寫出循環。 – twotwotwo 2014-09-13 22:00:46

回答

1

函數fmt.Printf("%v\n", data_interface)完全符合你的要求。它將打印傳遞給它的整個地圖或數組。

你可以在這裏找到實現:http://golang.org/src/pkg/fmt/print.go?h=printArg#L708

附近的printArg年底該生產線是關鍵:

return p.printReflectValue(reflect.ValueOf(arg), verb, plus, goSyntax, depth 

它使用了「反映」包:http://golang.org/pkg/reflect/查詢參數的類型。在p.printReflectValue裏面:http://golang.org/src/pkg/fmt/print.go?h=printArg#L862你會看到處理地圖和結構的兩種情況。然後它通過printValue使用遞歸來管理內容。

這裏有一些代碼需要反射一個結構,然後將它變回另一個正確類型的變量。你不能從一個任意類型改變爲另一個類型,使用它必須合法才能將類型從一個類型轉換爲另一個類型而不使用反射。

package main 

import (
    "fmt" 
    "reflect" 
) 

type Player string 

type Board struct { 
    Tboard [9]string 
    Player1 Player 
    Player2 Player 
} 

// ignore this function contents, I grabbed it from elsewhere. 
func makeBoard() *Board { 
    b := &Board{Tboard: [9]string{}} 
    for x := 0; x < len(b.Tboard); x++ { 
     b.Tboard[x] = "X" 
     fmt.Println(b.Tboard[x]) 
    } 
    b.Player1 = "George" 
    b.Player2 = "Tim" 

    fmt.Printf("Len: %v\n", len(b.Tboard)) // => 9 

    fmt.Printf("Contents: %v\n", b) 
    fmt.Printf("Syntax: %#v\n", b) 
    fmt.Printf("Type: %T\n", b) 
    fmt.Println("Board:", b.Tboard) 
    return b 
} 

func main() { 
    myBoard := makeBoard() 

    v := reflect.ValueOf(*myBoard) // v is of type Value 
    t := v.Type() 

    fmt.Printf("Value: %v %T\n", v, v) 
    fmt.Printf("Type: %v %T\n", t, t) 

    // would be a switch 
    if t == reflect.TypeOf(*myBoard) { 
     var b2 Board 

     b2 = v.Interface().(Board) // cast the type interface{} into type Board 
     fmt.Printf("v converted back to: %#v\n", b2) 
    } else { 
     fmt.Printf("t is not recognized") 
    } 

} 

注意的v類型爲main.Board,完整的包名稱,而不是Board。 任何你想要這樣做的結構都必須有一個導出類型以使反射起作用。

+0

偉大的這解決我的大部分問題。所以現在我可以將'reflect.Value'傳入我的'DoTheThing'函數。但是,在'DoTheThing'中,我會如何將傳遞的'reflect.Value'返回到它知道期待的基礎'* MyRandomComplicatedStructType'中? – Nat 2014-09-13 18:27:20

+1

請參閱附加代碼。它回答了嗎? – AndrewN 2014-09-14 00:13:29

1

爲了您的示例工作,你需要建立的interface{}陣列(或地圖),爲了正確的類型進行檢測:

// This won't work, as the .(type) would be []S 
someS := []S{S{}} 
DoTheThingToAllTheThings(someS) 

// This will: it will go in case []interface{}: 
someSI := make([]interface{}, len(someS)) 
for i, s := range someS { 
    someSI[i] = s 
} 
DoTheThingToAllTheThings(someSI) 

看到一個full example here

但這意味着您仍然可以在DoTheThing函數中使用interface{}
這裏沒有真正的通用名,正如我在「What would generics in Go be?」中提到的。

+0

對自己的注意:這是我在堆棧溢出(72個月)中的第11000個答案**,在[第10000個答案](http://stackoverflow.com/a/23909654/6309)後不到4個月。在此之前,[第9000個答案](http://stackoverflow.com/a/20683667/6309),[第8000個答案](http://stackoverflow.com/a/17569094/6309),[第7000個答案](http: (http://stackoverflow.com/a/11644343/6309),[5000th answer](http://stackoverflow.com/a/7917396/) 6309),[4000th答案](http://stackoverflow.com/a/4953561/6309),[3000th答案](http://stackoverflow.com/a/3074849/6309),... – VonC 2014-09-22 08:35:04