最近用 go 写程序时遇到一个问题——求任意类型切片的长度。作为一个初学者,刚刚学了接口和切片,知道了每个类型都实现了一个空接口 interface{},如果将接口类型作为函数的参数,那它应该是可以接收任意类型的实参的,带着这样的想法就写出了下面的代码:
func size(ins []interface{}) int {
return len(ins)
}
然后调用
a := []int{1, 2, 3, 4}
fmt.Println(size(a))
但编译的时候报了以下错误:
cannot use a (type []int) as type []interface {} in argument to size
从报错的信息来看,是 go 语言不支持将任意类型的切片转换为接口切片所导致的,为了确定是 go语言本身不支持所导致的以及探究不支持的原因,于是在网上查阅了一些资料,最权威的应该是来自于 go 的官方文档【https://github.com/golang/go/wiki/InterfaceSlice】。这上边的解释是说,由于非接口类型的切片与接口类型的切片在内存中的空间布局不一样,如果要做这样的隐式转换,将会比较耗时,因此 go 不支持此种转换。如果确实需要用到传接口切片的情况,则需要由程序员自己来提前做转换,在传参的时候确保实参是接口切片类型,这样才能通过编译,这也是官方推荐的做法,代码如下所示:
// 获得一个int类型的切片
var dataSlice []int = foo()
// 创建接口类型的切片
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
// 依次转换每个元素
for i, d := range dataSlice {
interfaceSlice[i] = d
}
// 调用上面的size方法
size(interfaceSlice)
如果按照上面的写法来传参,那么求切片的长度就显得太费劲了,还不如直接调用 len(dataSlice) 完事。事情发展到这里,有点不甘心,于是继续查资料,发现 go 语言的反射机制可以解决这个问题。首先将上面的 size 函数的参数改为接口类型 (a interface{}),然后在里面用 reflect.TypeOf(a).Kind() 来判断实参的类型,如果是切片类型,则用 reflect.ValueOf() 来获得该切片,最后返回切片的长度,代码如下所示:
func Size(a interface{}) int {
if reflect.TypeOf(a).Kind() != reflect.Slice {
return -1
}
ins := reflect.ValueOf(a)
return ins.Len()
}