2014-09-13 74 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