存储切片的接口上的范围

Posted

技术标签:

【中文标题】存储切片的接口上的范围【英文标题】:range over interface which stores a slice存储切片的接口上的范围 【发布时间】:2012-12-11 03:22:36 【问题描述】:

假设您有一个接受t interface 的函数。如果确定t是一个切片,我如何range覆盖那个切片?

func main() 
    data := []string"one","two","three"
    test(data)
    moredata := []int1,2,3
    test(data)


func test(t interface) 
    switch reflect.TypeOf(t).Kind() 
    case reflect.Slice:
        // how do I iterate here?
        for _,value := range t 
            fmt.Println(value)
        
    

去游乐场示例:http://play.golang.org/p/DNldAlNShB

【问题讨论】:

为什么不让函数采用 []interface 来代替?您是否尝试处理多种复杂类型? 是的,它用于模板系统,因此 interface 可以是映射、结构、切片或数组。实际代码中还有很多case语句,但为了让问题更简洁,我把它们从帖子中删除了。 Jeremy,[]string 不是 []interface 的子类型,因此不能使用 []string 或 []int 调用 func([]interface) 函数等。如果在 Go 中有一个特性,可以有一个表示“某物切片”的类型,那么您可以在其中迭代元素作为 interface,但不幸的是,您需要为此进行反射。 @JeremyWall 您不能使用 []interface 作为切片。可以参考here。 【参考方案1】:

好吧,我使用了reflect.ValueOf,然后如果它是一个切片,您可以调用Len()Index() 的值来获取切片和索引处元素的len。我认为您无法使用范围操作来执行此操作。

package main

import "fmt"
import "reflect"

func main() 
    data := []string"one","two","three"
    test(data)
    moredata := []int1,2,3
    test(moredata)
 

func test(t interface) 
    switch reflect.TypeOf(t).Kind() 
    case reflect.Slice:
        s := reflect.ValueOf(t)

        for i := 0; i < s.Len(); i++ 
            fmt.Println(s.Index(i))
        
    

去游乐场示例:http://play.golang.org/p/gQhCTiwPAq

【讨论】:

效果很好,谢谢。唯一要添加的是s.Index(i) 返回一个reflect.Value,所以在我的情况下,我需要s.Index(i).Interface() 来引用实际值。 如果你有一个指向interface 中切片的指针,你会怎么做?例如moredata := &amp;[]int1,2,3 回答我的问题:如果您有指向切片而不是切片的指针,则需要使用 Elem() 来获取基础值。例如reflect.TypeOf(reflect.ValueOf(t).Elem().Interface()).Kind() 如果我想从没有任何结构的接口切片中获取接口字段的值怎么办。我已经尝试过这个解决方案,但仅限于从切片而不是字段的实际值获取接口引用。 非常感谢,这个技巧为我节省了很多时间(也让我头疼!)【参考方案2】:

如果您知道预期的类型,则无需使用反射。你可以使用type switch,像这样:

package main

import "fmt"

func main() 
    loop([]string"one", "two", "three")
    loop([]int1, 2, 3)


func loop(t interface) 
    switch t := t.(type) 
    case []string:
        for _, value := range t 
            fmt.Println(value)
        
    case []int:
        for _, value := range t 
            fmt.Println(value)
        
    

Check out the code on the playground.

【讨论】:

这不会在数组上工作,只能在切片上工作 @user1028741 不,该代码适用于包括数组在内的所有类型 + 您始终可以从数组 array[:] 中获取切片。 但是数组的类型以它的实际容量为准。 [3]int 与 [2]int 或 []int 的类型不同。因此,如果 't' 的类型实际上是一个数组,则相关情况将不适用。 我的意思是你的例子不适用于数组。我在这里改了:play.golang.org/p/DAsg_0aXz-r 如果你一开始不知道参数的类型,你就不能轻易地让它工作。为了使用您的技巧(数组 [:]),您必须使用反射来确定它是一个数组,然后转换为数组 - 然后生成它的一部分。这就是为什么我说它适用于切片,而不是数组。显然,只要付出足够的努力,您就可以从数组中产生一个切片......【参考方案3】:

interface 的行为方式有一个例外,@Jeremy Wall 已经给出了指针。如果传递的数据最初定义为 []interface。

package main

import (
    "fmt"
)

type interfaceSliceType []interface

var interfaceAsSlice interfaceSliceType

func main() 
    loop(append(interfaceAsSlice, 1, 2, 3))
    loop(append(interfaceAsSlice, "1", "2", "3"))
    // or
    loop([]interface[]string"1", []string"2", []string"3")
    fmt.Println("------------------")


    // and of course one such slice can hold any type
    loop(interfaceSliceType"string", 999, map[int]string3: "three")


func loop(slice []interface) 
    for _, elem := range slice 
        switch elemTyped := elem.(type) 
        case int:
            fmt.Println("int:", elemTyped)
        case string:
            fmt.Println("string:", elemTyped)
        case []string:
            fmt.Println("[]string:", elemTyped)
        case interface:
            fmt.Println("map:", elemTyped)
        
    

输出:

int: 1
int: 2
int: 3
string: 1
string: 2
string: 3
[]string: [1]
[]string: [2]
[]string: [3]
------------------
string: string
int: 999
map: map[3:three]

try it out

【讨论】:

【参考方案4】:

扩展 masebase 提供的答案,您可以使用如下函数概括 interface 切片上的迭代:

func forEachValue(ifaceSlice interface, f func(i int, val interface)) 
    v := reflect.ValueOf(ifaceSlice)
    if v.Kind() == reflect.Ptr 
        v = v.Elem()
    
    if v.Kind() != reflect.Slice 
        panic(fmt.Errorf("forEachValue: expected slice type, found %q", v.Kind().String()))
    

    for i := 0; i < v.Len(); i++ 
        val := v.Index(i).Interface()
        f(i, val)
    

然后,你可以这样使用它:

func main() 
    data := []string"one","two","three"
    test(data)
    moredata := []int1,2,3
    test(data)


func test(sliceIface interface) 
    forEachValue(sliceIface, func(i int, value interface) 
      fmt.Println(value)
    

【讨论】:

【参考方案5】:

函数中的一个共同点

import (
    "fmt"
    "reflect"
)

func In(item interface, list interface) (ret bool, err error) 
    defer func() 
        if r := recover(); r != nil 
            ret, err = false, fmt.Errorf("%v", r)
        
    ()
    itemValue, listValue := reflect.ValueOf(item), reflect.ValueOf(list)
    for i := 0; i < listValue.Len(); i++ 
        if listValue.Index(i).Interface() == itemValue.Interface() 
            return true, nil
        
    
    return false, nil

不是很优雅,但效果很好

fmt.Println(In(1, []int(2, 1, 3)))

【讨论】:

以上是关于存储切片的接口上的范围的主要内容,如果未能解决你的问题,请参考以下文章

为什么Python3的范围/切片对象不支持其他范围/切片的包含测试?

切片范围的循环索引

golang 循环切片通过golang中的切片使用范围

为啥切片和范围上限是排他的?

Go 语言范围(Range)

Go 语言范围(Range)