背景
您的“简单方法”的问题在于,切片
(任何类型)是一个由指针组成的struct-typed
和两个整数;指针包含的地址
底层(支持)数据数组,整数包含
len() 和 cap() 内置函数为该切片返回什么。
换句话说,切片是一种视图到数组。
那么,在 Go 中,没有类型转换的概念;只有
类型转换,并且这些转换可能只发生在
具有相同基础表示的类型¹。
由于切片和数组可能没有相同的底层
表示(数组实际上是一个连续的内存块
大小刚好足以包含数组的所有元素),
您所谓的类型转换可能不合法。
可能的解决方案
有两种可能的解决方案。
最简单的方法是从切片中复制数据
支持数组到一个新分配的数组中:
var (
src = []byte{1, 2, 3, 4, 5, 6, 7, 8}
dst [8]uint8
)
copy(dst[:], src[:8])
请注意,切片和切片之间存在固有的差异
数组类型:数组类型同时编码其元素的类型
及其长度(即长度是类型的一部分),
而切片类型仅编码其元素的类型
(并且在运行时可以是任意长度)。
这意味着您可能需要在此之前进行检查
复制确保源切片正好有 8
元素,即len(src) == len(dst)。
这个不变量可能由其他代码强制执行,但我认为
我会提前警告你:如果src 少于 8
元素,src[:8] 表达式将在运行时恐慌,
如果它包含更多,那么问题是是否
只复制其中的前 8 个正是我们所需要的。
第二种方法(诚然更麻烦)是重复使用
切片的底层数组:
import "unsafe"
var (
src = []byte{1, 2, 3, 4, 5, 6, 7, 8}
dstPtr *[8]uint8
)
if len(src) != len(*dstPtr) {
panic("boom")
}
dstPtr = (*[8]uint8)(unsafe.Pointer(&src[0]))
这里,我们刚刚获取了第一个元素的地址
包含在切片的底层数组中并执行
一个“脏”的两相类型转换,使获得的
指针类型为*[8]uint8——即“一个地址
8 个 uint8s" 的数组。
注意两个警告:
-
结果指针现在指向
与原始切片相同的内存块。
这意味着现在可以通过
slice 和我们得到的指针。
-
一旦您决定分配数组的数据
到[8]uint8 类型的变量(并将其作为参数传递
到该类型的函数参数),您将取消引用
那个指针(比如*dstPtr),在那一刻
数组的数据将被复制。
我特别提到这一点,因为人们经常使用
像这样的黑客来拉出支持阵列
精确切片以尝试不复制内存。
TL;DR
复制数据(假设验证了
len(src) == len(dst) 保持不变)。
复制 8 个字节很快(在典型的 64 位 CPU 上,这将是
一条MOV 指令,或最多两条),代码将
直截了当。
只有当你真的
需要在一些关键的热路径上进行优化。
在这种情况下,请广泛评论解决方案并注意
不会意外取消引用您的指针。
¹这条规则有一些明显的例外:
-
[]byte 可以类型转换为 string,反之亦然。
-
string 可以类型转换为 []rune,反之亦然。
-
int 可以类型转换为 string(但从 Go 1.15 开始,go vetgives a warning about it,将来可能会禁止此功能)。