【问题标题】:How to accept different types of slices in the same function? [duplicate]如何在同一个函数中接受不同类型的切片? [复制]
【发布时间】:2019-05-12 01:58:15
【问题描述】:

我有一个函数removeFrom 从切片中删除一个项目。它接受 float64 切片和索引:

func removeFrom(slice []float64, index int) []float64 {
    if len(slice) > index {
        return append(slice[:index], slice[index+1:]...)
    }
}

它工作正常,但现在我也必须从整数切片中删除。那么我怎么能改变它以接受这两种类型(并返回给定类型的切片)?我尝试使用空接口,但显然我必须在函数内部进行一些转换,但我不知道该怎么做。

【问题讨论】:

    标签: generics go slice


    【解决方案1】:

    简短回答?你不能。

    长答案,你还是不能直接做,但是:

    func removeFrom(slice interface{}, index int) interface{} {
        switch slice := slice.(type) {
        case []float64:
            if len(slice) > index {
                return append(slice[:index], slice[index+1:]...)
            }
        case []int64:
            if len(slice) > index {
                return append(slice[:index], slice[index+1:]...)
            }
        case []int:
            if len(slice) > index {
                return append(slice[:index], slice[index+1:]...)
            }
        default:
            log.Panicf("unknown type: %T", slice)
        }
    }
    

    【讨论】:

    • 哇.. 好的,我认为最好为每种类型使用单独的方法:P 谢谢!
    • case []float64, []int64, []int: doSmt 呢?
    • 我尝试运行上面的代码得到invalid argument slice (type interface {}) for len...
    • @u_mulder 它不起作用,因为切片将输入interface{}
    【解决方案2】:

    Go 不支持泛型,所有切片类型都没有“共同祖先”(例如,[]interface{}[]int 不“兼容”,有关详细信息,请参阅 Cannot convert []string to []interface {})。

    因此,如果您希望您的函数接受任何切片类型,则必须使用interface{}(用于“传入”参数和返回类型)。但是现在您有一个(接口)包装器值,您不能对其应用切片,也不能将其传递给内置的 append() 函数。

    您可以将type assertiontype switches 用于已知类型,但您必须为每个类型重复代码,所以这并不是真正的领先一步。

    实际上有一种方法可以使用 reflection 创建适用于所有切片类型的 removeFrom() 函数。

    reflect.Value 是一种描述任何 Go 值的类型。它支持不同类型的 Go 值的方法,包括切片。

    我们感兴趣的是Value.Slice() 方法:

    func (v Value) Slice(i, j int) Value
    

    我们可以用它来切片。好的。这是我们的元素去除算法中的一个关键点。仍然需要“加入”2 个切片,即可移动元素之前的切片和可移动元素之后的切片。幸运的是,reflect 包也支持这个:reflect.AppendSlice():

    func AppendSlice(s, t Value) Value
    

    作为最后剩下的key,我们可以使用Value.Len()来获取任意切片的长度。

    我们现在拥有了我们的通用 removeFrom() 函数所需的一切,这非常简单:

    func removeFrom(s interface{}, idx int) interface{} {
        if v := reflect.ValueOf(s); v.Len() > idx {
            return reflect.AppendSlice(v.Slice(0, idx), v.Slice(idx+1, v.Len())).Interface()
        }
        return s
    }
    

    真的,仅此而已。测试它:

    for i := 0; i < 4; i++ {
        fmt.Println(removeFrom([]int{0, 1, 2}, i), "missing:", i)
    }
    for i := 0; i < 4; i++ {
        fmt.Println(removeFrom([]string{"zero", "one", "two"}, i), "missing:", i)
    }
    

    输出(在Go Playground 上试试):

    [1 2] missing: 0
    [0 2] missing: 1
    [0 1] missing: 2
    [0 1 2] missing: 3
    [one two] missing: 0
    [zero two] missing: 1
    [zero one] missing: 2
    [zero one two] missing: 3
    

    注意事项:

    此解决方案使用反射,因此它比不使用反射但具有“连接”的具体支持类型的另一个解决方案要慢。快速基准测试表明,这种通用解决方案比有线输入类型的非反射慢 2.5 倍。应该权衡性能或便利性/通用解决方案是否更重要。或者你可以将它与具体类型结合起来:你可以添加一个类型开关来处理频繁的类型,并且只有在类型开关没有处理实际的具体类型时才恢复到这个通用解决方案。

    【讨论】:

    • 请注意,与类型断言相比,这非常慢。
    • @OneOfOne 这个通用解决方案使用反射,所以是的,它会比“连接”具体类型的东西慢。 “可怕”是一个强有力的词,因为我预计差异是数量级。快速基准测试显示,一般的反射解决方案要慢 2.5 倍。
    • 我无法编辑它,但是 horribly 是一个很强烈的词,我深表歉意。
    猜你喜欢
    • 1970-01-01
    • 2016-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-14
    相关资源
    最近更新 更多