dedecms 调用网站名称,wordpress 我爱水煮鱼,重庆做营销型网站建设公司,网页特效源码网站文章目录 前言官方解释内存布局程序运行中的内存布局通用方法 前言
在 Go 中#xff0c;如果 interface{} 作为函数参数的话#xff0c;是可以传任意参数的#xff0c;然后通过类型断言来转换。 举个例子#xff1a;
package mainimport fmtfunc foo(v inter… 文章目录 前言官方解释内存布局程序运行中的内存布局通用方法 前言
在 Go 中如果 interface{} 作为函数参数的话是可以传任意参数的然后通过类型断言来转换。 举个例子
package mainimport fmtfunc foo(v interface{}) {if v1, ok1 : v.(string); ok1 {fmt.Println(v1)} else if v2, ok2 : v.(int); ok2 {fmt.Println(v2)}
}func main() {foo(233)foo(666)
}不管是传 int 还是 string最终都能输出正确结果。 那么既然是这样的话我就有一个疑问了拿出我举一反三的能力。是否可以将 []T 转换为 []interface 呢
比如下面这段代码
func foo([]interface{}) { /* do something */ }func main() {var a []string []string{hello, world}foo(a)
}很遗憾这段代码是不能编译通过的如果想直接通过 b : []interface{}(a) 的方式来转换还是会报错
cannot use a (type []string) as type []interface {} in function argument正确的转换方式需要这样写
b : make([]interface{}, len(a), len(a))
for i : range a {b[i] a[i]
}本来一行代码就能搞定的事情却非要让人写四行是不是感觉很麻烦那为什么 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 N2 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 Nsizeof(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. 大概意思就是说主要有两方面原因
[]interface{} 类型并不是 interface它是一个切片只不过碰巧它的元素是 interface[]interface{} 是有特殊内存布局的跟 interface 不一样。
下面就来详细说说是怎么个不一样。
内存布局
首先来看看 slice 在内存中是如何存储的。在源码中它是这样定义的
// src/runtime/slice.gotype slice struct {array unsafe.Pointerlen intcap int
}array 是指向底层数组的指针len 是切片的长度cap 是切片的容量也就是 array 数组的大小。
举个例子创建如下一个切片
is : []int64{0x55, 0x22, 0xab, 0x9}那么它的布局如下图所示
假设程序运行在 64 位的机器上那么每个「正方形」所占空间是 8 bytes。上图中的 ptr 所指向的底层数组占用空间就是 4 个「正方形」也就是 32 bytes。
接下来再看看 []interface{} 在内存中是什么样的。
回答这个问题之前先看一下 interface{} 的结构Go 中的接口类型分成两类
iface 表示包含方法的接口eface 表示不包含方法的空接口。
源码中的定义分别如下
type iface struct {tab *itabdata unsafe.Pointer
}type eface struct {_type *_typedata unsafe.Pointer
}具体细节我们不去深究但可以明确的是每个 interface{} 包含两个指针 会占据两个「正方形」。第一个指针指向 itab 或者 _type第二个指针指向实际的数据。
所以它在内存中的布局如下图所示
因此不能直接将 []int64 直接传给 []interface{}。
程序运行中的内存布局
接下来换一个更形象的方式从程序实际运行过程中看看内存的分布是怎么样的 看下面这样一段代码
package mainvar sum int64func 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)
}我们使用 Delve 来进行调试可以点击这里进行安装。
dlv debug slice-layout.go
Type help for list of commands.
(dlv) break slice-layout.go:27
Breakpoint 1 set at 0x105a3fe for main.main() ./slice-layout.go:27
(dlv) cmain.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: }打印 is 的地址
(dlv) p is
(*[]int64)(0xc00003a740)接下来看看 slice 在内存中都包含了哪些内容
(dlv) x -fmt hex -len 32 0xc00003a740
0xc00003a740: 0x10 0xa7 0x03 0x00 0xc0 0x00 0x00 0x00
0xc00003a748: 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xc00003a750: 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xc00003a758: 0x00 0x00 0x09 0x00 0xc0 0x00 0x00 0x00每行有 8 个字节也就是上文说的一个「正方形」。第一行是指向数据的地址第二行是 4表示切片长度第三行也是 4表示切片容量。 再来看看指向的数据到底是怎么存的
(dlv) x -fmt hex -len 32 0xc00003a710
0xc00003a710: 0x55 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xc00003a718: 0x22 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xc00003a720: 0xab 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xc00003a728: 0x09 0x00 0x00 0x00 0x00 0x00 0x00 0x00这就是一片连续的存储空间保存着实际数据。 接下来用同样的方式再来看看 iis 的内存布局。
(dlv) p iis
(*[]interface {})(0xc00003a758)
(dlv) x -fmt hex -len 32 0xc00003a758
0xc00003a758: 0x00 0x00 0x09 0x00 0xc0 0x00 0x00 0x00
0xc00003a760: 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xc00003a768: 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xc00003a770: 0xd0 0xa7 0x03 0x00 0xc0 0x00 0x00 0x00切片的布局和 is 是一样的主要的不同是所指向的数据
(dlv) x -fmt hex -len 64 0xc000090000
0xc000090000: 0x00 0xe4 0x05 0x01 0x00 0x00 0x00 0x00
0xc000090008: 0xa8 0xee 0x0a 0x01 0x00 0x00 0x00 0x00
0xc000090010: 0x00 0xe4 0x05 0x01 0x00 0x00 0x00 0x00
0xc000090018: 0x10 0xed 0x0a 0x01 0x00 0x00 0x00 0x00
0xc000090020: 0x00 0xe4 0x05 0x01 0x00 0x00 0x00 0x00
0xc000090028: 0x58 0xf1 0x0a 0x01 0x00 0x00 0x00 0x00
0xc000090030: 0x00 0xe4 0x05 0x01 0x00 0x00 0x00 0x00
0xc000090038: 0x48 0xec 0x0a 0x01 0x00 0x00 0x00 0x00仔细观察上面的数据偶数行内容都是相同的这个是 interface{} 的 itab 地址。奇数行内容是不同的指向实际的数据。 打印地址内容
(dlv) x -fmt hex -len 8 0x010aeea8
0x10aeea8: 0x55 0x00 0x00 0x00 0x00 0x00 0x00 0x00
(dlv) x -fmt hex -len 8 0x010aed10
0x10aed10: 0x22 0x00 0x00 0x00 0x00 0x00 0x00 0x00
(dlv) x -fmt hex -len 8 0x010af158
0x10af158: 0xab 0x00 0x00 0x00 0x00 0x00 0x00 0x00
(dlv) x -fmt hex -len 8 0x010aec48
0x10aec48: 0x09 0x00 0x00 0x00 0x00 0x00 0x00 0x00很明显通过打印程序运行中的状态和我们的理论分析是一致的。
通用方法
通过以上分析我们知道了不能转换的原因那有没有一个通用方法呢因为我实在是不想每次多写那几行代码。 也是有的用反射 reflect但是缺点也很明显效率会差一些不建议使用。
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 inputif s.IsNil() {return nil}ret : make([]interface{}, s.Len())for i : 0; i s.Len(); i {ret[i] s.Index(i).Interface()}return ret
}还有其他方式吗答案就是 Go 1.18 支持的泛型这里就不过多介绍了大家有兴趣的话可以继续研究。