在Go中,如果使用interface{}作为函数参数,可以传递任何参数,然后通过类型断言进行转换。例如:packagemainimport"fmt"funcfoo(vinterface{}){ifv1,ok1:=v.(string);ok1{fmt.Println(v1)}elseifv2,ok2:=v.(int);ok2{fmt.Println(v2)}}funcmain(){foo(233)foo("666")}不管传int还是string,最后都能输出正确的结果。好吧,既然如此,那我就有一个疑问了,我得用我的能力,从一个实例中推断出其他的情况。是否可以将[]T转换为[]interface?例如,下面的代码:funcfoo([]interface{}){/*dosomething*/}funcmain(){vara[]string=[]string{"hello","world"}foo(a)}不幸的是,这段代码无法编译。如果想直接通过b:=[]interface{}(a)转换,还是会报错:cannotusea(type[]string)astype[]函数参数中接口{}的正确转换方式需要这样写:b:=make([]interface{},len(a),len(a))fori:=rangea{b[i]=a[i]}可以做的事情一行代码,却要写四行,不觉得麻烦吗?那么为什么Go不支持它呢?我们往下看。官方解释这个问题在官方Wiki中是有答案的,我复制出来放下面:首先是类型[]接口{}的变量不是接口!它是一个切片,其元素类型恰好是interface{}。但即便如此,人们可能会说意思很清楚。嗯,是吗?具有type[]interface{}的变量具有特定的内存布局,在编译时已知。每个interface{}占用两个词(一个词表示所包含内容的类型,另一个词表示包含的数据或指向它的指针)。因此,长度为N且类型为[]interface{}的切片由N*2字长的数据块支持。这不同于支持具有类型[]MyType和相同长度的切片的数据块。它的数据块将是N*sizeof(MyType)wordslong。结果是您无法快速将type[]MyType的内容分配给type[]interface{}的内容;大概念就是说,主要有两个方面原因:[]interface{}类型而不是interface,它是一个片段,只是不过巧它的元素是int界面;[]interface{}有一个特殊的内存布局,不同于interface。下面就来详细说说,它有何不同。内存布局我们先来看看切片是如何存储在内存中的。在源码中,是这样定义的://src/runtime/slice.gotypeslicestruct{arrayunsafe.Pointerlenintcapint}array是指向底层数组的指针;len是切片的长度;cap是切片Capacity,即array数组的大小。例如创建如下切片:is:=[]int64{0x55,0x22,0xab,0x9}那么它的布局如下图所示:假设程序运行在64位机器上,那么每个“方块”的占用空间为8字节。上图中ptr指向的底层数组占用的空间为4个“方格”,即32个字节。接下来我们看看[]interface{}在内存中是什么样子的。在回答这个问题之前,我们先看一下interface{}的结构。Go中的接口类型分为两类:iface表示包含方法的接口;eface表示不包含方法的空接口。源码中的定义如下:typeifacestruct{tab*itabdataunsafe.Pointer}typeefacestruct{_type*_typedataunsafe.Pointer}我们就不细说了,但是很清楚每个接口{}包含两个指针,分别占据两个“方块”。第一个指针指向itab或_type;第二个指针指向实际数据。所以它在内存中的布局如下图所示:因此,[]int64不能直接传给[]interface{}。程序运行过程中的内存布局被更改为更直观的方式。从程序的实际运行过程来看,内存的分布是怎样的呢?看下面这段代码:packagemainvarsumint64funcaddUpDirect(s[]int64){fori:=0;我<长度;i++{sum+=s[i]}}funcaddUpViaInterface(s[]interface{}){fori:=0;我<长度;i++{sum+=s[i].(int64)}}funcmain(){is:=[]int64{0x55,0x22,0xab,0x9}addUpDirect(is)iis:=make([]interface{},len(is))对于i:=0;我<长度(是);i++{iis[i]=is[i]}addUpViaInterface(iis)}我们使用Delve进行调试,您可以点击这里安装。dlvdebugslice-layout.goType'help'命令列表。(dlv)breakslice-layout.go:27Breakpoint1setat0x105a3feformain.main()./slice-layout.go:27(dlv)c>main.main()./slice-layout.go:27(hitsgoroutine(1):1total:1)(PC:0x105a3fe)22:iis:=make([]interface{},len(is))23:对于我:=0;我<长度(是);i++{24:iis[i]=is[i]25:}26:=>27:addUpViaInterface(iis)28:}打印is的地址:(dlv)P&IS(*[]int64)(0xc00003A740)NexttoseewhatcontentsofSlicecontaininmemory:(DLV)X-FMTHex-Leen320xc00003A74003A740:0xa70x030x000x000x00x003A748:0x003A748:0x00X00x0448:0x00X00X8:0x00x0448:0x00x0448:0x00x0x48:0x00x0448:0x00x0448:0x00x0448:0x00x0448:0x00x0448:0x0448:0x00x0x48:0x0448:0x048:0x000x000x000x000x000x000xc00003A750:0x040x000x000x000x000x000x00xc00003A758:0x000x000x000x000x000x000x00has8bytesperlineperline.第一行是指向数据的地址;第二行是4,表示切片长度;第三行也是4,表示分片容量。再来看看指向的数据到底是怎么存的:(dlv)x-fmthex-len320xc00003a7100xc00003a710:0x550x000x000x000x000x000x000x000xc00003a718:0x220x000x000x000x000x000x000x000xc00003a720:0xab0x000x000x000x000x000x000x000xc00003a728:0x090x000x000x000x000x000x000x00Thisisacontinuousstoragespacethatstorestheactualdata.Next,inthesameway,let'stakealookatthememorylayoutofiis.(dlv)p&iis(*[]interface{})(0xc00003a758)(dlv)x-fmthex-len320xc00003a7580xc00003a758:0x000x000x090x000xc00x000x000x000xc00003a760:0x040x000x000x000x000x000x000x000xc00003a768:0x040x000x000x000x000x000x000x000xc00003a770:0xd00xa70x030x000xc00x000x000x00切片的布局和is是一样的,主要的不同是所指向的数据:(dlv)x-fmthex-len640xc0000900000xc000090000:0x000xe40x050x010x000x000x000x000xc000090008:0xa80xee0x0a0x010x000x000x000x000xc000090010:0x000xe40x050x010x000x000x000x000xc000090018:0x100xed0x0a0x010x000x000x000x000xc000090020:0x000xe40x050x010x000x000x000x000xc000090028:0x580xf10x0a0x010x000x000x000x000xc000090030:0x000xe40x050x010x000x000x000x000xc000090038:0x480xec0x0a0x010x000x000x000x00仔细观察上面的数据,偶数行内容相同,这是interface{}的itab地址,奇数行内容不同,指向实际数据。打印地址内容:(DLV)X-FMTHex-Leen80x010Aeea80x10aeea8:0x550x000x000x000x000x00(DLV)x-FMTHex-Len80x010Aed10:0x000x000x000x000x00(dlv)-(dlv)LeenHEX0x010AF1580X10AF158:0x000x000x000x000x000x000x00(DLV)x-FMTHex-Len80x010AEC480X10AEC48:0x000x000x000x000x000x00很明显。是一致的。通用方法通过上面的分析,我们知道了不能转换的原因,那么有没有通用的方法呢?因为实在不想每次都写那几行代码。用reflect也是可以的,但是缺点也很明显,效率会差一些,所以不推荐使用。funcInterfaceSlice(sliceinterface{})[]interface{}{s:=reflect.ValueOf(slice)ifs.Kind()!=reflect.Slice{panic("InterfaceSlice()givenanon-slicetype")}//保持nil和空切片输入的区别ifs.IsNil(){returnnil}ret:=make([]interface{},s.Len())fori:=0;我
