如何在同一个函数中接受不同类型的切片?

时间:2016-05-22 16:01:28

标签: generics go slice

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

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

它工作正常,但现在我必须从整数切片中删除。那么我怎么能改变它来接受这两种类型(并返回给定类型的切片)?我试图使用空接口,但显然我必须在函数内部进行一些转换,但我没有找到如何做到这一点。

2 个答案:

答案 0 :(得分:3)

简短回答?你不能。

答案很长,你仍然不能直接这样做,但是:

Generational (Example: .NET)
Background (Example: .NET)
Reference counting (Example: COM)
Concurrent (Example: Oracle CMS)
Deterministic/real-time (Example: Azul Zing)
Region-based (Example: Oracle G1)
Incremental (Example: Oracle CLP)

答案 1 :(得分:3)

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

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

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

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

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

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

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

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

func AppendSlice(s, t Value) Value

作为最后剩余的密钥,我们可以使用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倍。无论性能还是便利/一般解决方案更重要,都应加权。或者您可以将它与具体类型结合起来:您可以添加一个类型开关来处理频繁类型,如果类型开关没有处理实际的具体类型,则只返回到这个通用解决方案。