func foo([]interface{}){/* do something */}
func main(){
var a []string =[]string{"hello","world"}
foo(a)}
1.
2.
3.
4.
5.
6.
很遗憾,这段代码是不能编译通过的,如果想直接通过 b := []interface{}(a) 的方式来转换,还是会报错:
cannot use a (type []string)as type []interface {}in function argument
1.
正确的转换方式需要这样写:
b := make([]interface{}, len(a), len(a))
for i := range a {
b[i]= a[i]}
1.
2.
3.
4.
本来一行代码就能搞定的事情,却非要让人写四行,是不是感觉很麻烦?那为什么 Go 不支持呢?我们接着往下看。
官方解释
这个问题在官方 Wiki 中是有回答的,我复制出来放在下面:
The first is that a variable with type []interface{} is not an interface! It is a slice whose element type happens to be interface{}. But even given this, one might say that the meaning is clear. Well, is it? A variable with type []interface{} has a specific memory layout, known at compile time. Each interface{} takes up two words (one word for the type of what is contained, the other word for either the contained data or a pointer to it). As a consequence, a slice with length N and with type []interface{} is backed by a chunk of data that is N*2 words long. This is different than the chunk of data backing a slice with type []MyType and the same length. Its chunk of data will be N*sizeof(MyType) words long. The result is that you cannot quickly assign something of type []MyType to something of type []interface{}; the data behind them just look different.
package main
var sum int64
func addUpDirect(s []int64){
for i :=0; i < len(s); i++{
sum += s[i]}}
func addUpViaInterface(s []interface{}){
for i :=0; i < len(s); i++{
sum += s[i].(int64)}}
func main(){is:=[]int64{0x55,0x22,0xab,0x9}
addUpDirect(is)
iis := make([]interface{}, len(is))
for i :=0; i < len(is); i++{
iis[i]=is[i]}
addUpViaInterface(iis)}
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
我们使用 Delve 来进行调试,可以点击这里进行安装。
dlv debug slice-layout.go
Type 'help' for list of commands.
(dlv) break slice-layout.go:27
Breakpoint 1set at 0x105a3fe for main.main() ./slice-layout.go:27(dlv) c
> main.main() ./slice-layout.go:27(hits goroutine(1):1 total:1)(PC:0x105a3fe)22: iis := make([]interface{}, len(is))23: for i :=0; i < len(is); i++{24: iis[i]=is[i]25:}26:=>27: addUpViaInterface(iis)28:}
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
打印 is 的地址:
(dlv) p &is(*[]int64)(0xc00003a740)
1.
2.
接下来看看 slice 在内存中都包含了哪些内容:
(dlv) x -fmt hex -len 320xc00003a7400xc00003a740:0x100xa70x030x000xc00x000x000x000xc00003a748:0x040x000x000x000x000x000x000x000xc00003a750:0x040x000x000x000x000x000x000x000xc00003a758:0x000x000x090x000xc00x000x000x00
(dlv) x -fmt hex -len 320xc00003a7100xc00003a710:0x550x000x000x000x000x000x000x000xc00003a718:0x220x000x000x000x000x000x000x000xc00003a720:0xab0x000x000x000x000x000x000x000xc00003a728:0x090x000x000x000x000x000x000x00
1.
2.
3.
4.
5.
这就是一片连续的存储空间,保存着实际数据。
接下来用同样的方式,再来看看 iis 的内存布局。
(dlv) p &iis
(*[]interface {})(0xc00003a758)(dlv) x -fmt hex -len 320xc00003a7580xc00003a758:0x000x000x090x000xc00x000x000x000xc00003a760:0x040x000x000x000x000x000x000x000xc00003a768:0x040x000x000x000x000x000x000x000xc00003a770:0xd00xa70x030x000xc00x000x000x00
1.
2.
3.
4.
5.
6.
7.
切片的布局和 is 是一样的,主要的不同是所指向的数据:
(dlv) x -fmt hex -len 640xc0000900000xc000090000:0x000xe40x050x010x000x000x000x000xc000090008:0xa80xee0x0a0x010x000x000x000x000xc000090010:0x000xe40x050x010x000x000x000x000xc000090018:0x100xed0x0a0x010x000x000x000x000xc000090020:0x000xe40x050x010x000x000x000x000xc000090028:0x580xf10x0a0x010x000x000x000x000xc000090030:0x000xe40x050x010x000x000x000x000xc000090038:0x480xec0x0a0x010x000x000x000x00
func InterfaceSlice(slice interface{})[]interface{}{
s := reflect.ValueOf(slice)
if s.Kind()!= reflect.Slice{
panic("InterfaceSlice() given a non-slice type")}// Keep the distinction between nil and empty slice input
if s.IsNil(){
return nil
}
ret := make([]interface{}, s.Len())
for i :=0; i < s.Len(); i++{
ret[i]= s.Index(i).Interface()}
return ret
}
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
还有其他方式吗?答案就是 Go 1.18 支持的泛型,这里就不过多介绍了,大家有兴趣的话可以继续研究。