Go 不支持泛型,所有切片类型都没有“共同祖先”(例如,[]interface{} 与 []int 不“兼容”,有关详细信息,请参阅 Cannot convert []string to []interface {})。
因此,如果您希望您的函数接受任何切片类型,则必须使用interface{}(用于“传入”参数和返回类型)。但是现在您有一个(接口)包装器值,您不能对其应用切片,也不能将其传递给内置的 append() 函数。
您可以将type assertion 和type 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 倍。应该权衡性能或便利性/通用解决方案是否更重要。或者你可以将它与具体类型结合起来:你可以添加一个类型开关来处理频繁的类型,并且只有在类型开关没有处理实际的具体类型时才恢复到这个通用解决方案。