Go语言 如何将切片转换为数组?

f87krz0w  于 2022-12-07  发布在  Go
关注(0)|答案(7)|浏览(287)

我正在尝试写一个读取RPM文件的应用程序。每个块的开头都有一个魔术字符[4]byte
下面是我的结构体

type Lead struct {
  Magic        [4]byte
  Major, Minor byte
  Type         uint16
  Arch         uint16
  Name         string
  OS           uint16
  SigType      uint16
}

我正在尝试做以下事情:

lead := Lead{}
lead.Magic = buffer[0:4]

我正在网上搜索,不知道如何从切片到数组(不复制)。我总是可以使魔术[]byte(甚至uint64),但我更好奇的是,如果需要,我将如何从类型[]byte[4]byte

qq24tv8q

qq24tv8q1#

内建的方法 copy 只会将磁盘片段复制到磁盘片段,而不会将磁盘片段复制到数组。
您必须诱使 copy 认为数组是一个切片

copy(varLead.Magic[:], someSlice[0:4])

或者使用for循环进行复制:

for index, b := range someSlice {

    varLead.Magic[index] = b

}

或者像zupa那样使用文字。我已经添加到他们的工作示例中。
Go Playground

klsxnrf1

klsxnrf12#

你已经在这个结构体中分配了四个字节,并且想给这个四字节的部分赋值,如果不复制的话,没有概念上的方法可以做到。
查看copy内置函数以了解如何执行此操作。

j91ykkif

j91ykkif3#

试试这个:

copy(lead.Magic[:], buf[0:4])
gblwokeq

gblwokeq4#

第一个问题:
Go 1.181.19 1.20将支持从切片到数组的转换:golang/go issues 46505
因此,从Go语言1.18开始,分片copy 2的实现可以写成:

*(*[N]T)(d) = [N]T(s)

或者,如果允许转换以L值表示,则更简单:

[N]T(d) = [N]T(s)

在下一个Go语言1.17(2021年第3季度)中,无需复制,就可以将切片转换为数组 * 指针 *。
这被称为“非切片”,返回一个指向切片的底层数组的指针,同样,不需要任何复制/分配:

请参阅golang/go issue 395: spec: convert slice x into array pointer(现在已通过CL 216424/commit 1c26843实现)
将切片转换为数组指针会生成指向切片的基础数组的指针。
如果片段的长度小于数组的长度,就会发生执行阶段死机。

s := make([]byte, 2, 4)
s0 := (*[0]byte)(s)      // s0 != nil
s2 := (*[2]byte)(s)      // &s2[0] == &s[0]
s4 := (*[4]byte)(s)      // panics: len([4]byte) > len(s)

var t []string
t0 := (*[0]string)(t)    // t0 == nil
t1 := (*[1]string)(t)    // panics: len([1]string) > len(s)

因此,在您的示例中,假设Magic类型为*[4]byte

lead.Magic = (*[4]byte)(buffer)

注意:类型别名也会起作用:

type A [4]int
var s = (*A)([]int{1, 2, 3, 4})

为什么要转换为数组指针?如issue 395中所述:
这样做的一个动机是使用数组指针允许编译器在编译时对常量索引进行范围检查。
这样的函数:

func foo(a []int) int
{
   return a[0] + a[1] + a[2] + a[3];
}

可以变成:

func foo(a []int) int
{
   b := (*[4]int)(a)
   return b[0] + b[1] + b[2] + b[3];
}

允许编译器只检查所有边界一次,并给予有关超出范围索引的编译时错误。
还有:
一个常用的例子是为树节点或链接列表节点创建尽可能小的类,这样就可以将尽可能多的类塞进L1缓存行中。
这通过每个节点具有指向左子节点的单个指针,并且右子节点由指向左子节点+1的指针访问来完成。
这为右节点指针节省了8个字节。
要做到这一点,您必须预先分配向量或数组中的所有节点,以便它们在内存中按顺序布局,但当您需要性能时,这是值得的。
(This也有一个额外的好处,即预取器能够帮助沿着性能--至少在链表的情况下是这样)
在Go语言中,您几乎可以使用以下代码来执行此操作:

type node struct {
     value int
     children *[2]node
  }

只是无法从底层切片中获取*[2]node
Go 1.20(2023年第一季度):这用x1E8 F1 x、x1E9 F1 x、x1E10 F1 x和x1E11 F1 x来寻址。

egmofgnx

egmofgnx5#

您可以通过一次读取来完成整个操作,而不是分别阅读每个字段。如果字段是固定长度的,则可以执行以下操作:

lead := Lead{}

// make a reader to dispense bytes so you don't have to keep track of where you are in buffer
reader := bytes.NewReader(buffer)

// read into each field in Lead, so Magic becomes buffer[0:4],
// Major becomes buffer[5], Minor is buffer[6], and so on...
binary.Read(reader, binary.LittleEndian, &lead)
ej83mcc0

ej83mcc06#

开始1.20

你可以直接用T(x)转换语法把切片转换成数组。数组的长度不能大于切片的长度:

func main() {
    slice := []int64{10, 20, 30, 40}
    array := [4]int64(slice)
    fmt.Printf("%T\n", array) // [4]int64
}

1.17开始
从Go语言1.17开始,你可以直接将一个切片转换成一个数组 * 指针 *,使用Go语言的类型转换语法T(x),你可以做到这一点:

slice := make([]byte, 4)
arrptr := (*[4]byte)(slice)

请记住,数组的长度不得大于切片的长度,否则转换将出现混乱。

bad := (*[5]byte)(slice) // panics: slice len < array len

这种转换的优点是不进行任何复制,因为它只生成一个指向基础数组的指针。
当然,您可以解除数组指针的引用,以获得非指针数组变量,因此下面的代码也适用:

slice := make([]byte, 4)
var arr [4]byte = *(*[4]byte)(slice)

然而,解引用和赋值会巧妙地复制,因为arr变量现在被初始化为转换表达式产生的值。

v := []int{10,20}
a := (*[2]int)(v)

a[0] = 500
fmt.Println(v) // [500 20] (changed, both point to the same backing array)

w := []int{10,20}
b := *(*[2]int)(w)

b[0] = 500
fmt.Println(w) // [10 20] (unchanged, b holds a copy)

有人可能会想,为什么转换要检查切片长度 * 而不是容量 *(我检查了)。

func main() {
    a := []int{1,2,3,4,5,6}
    fmt.Println(cap(a)) // 6

    b := a[:3]
    fmt.Println(cap(a)) // still 6

    c := (*[3]int)(b)

    ptr := uintptr(unsafe.Pointer(&c[0]))
    ptr += 3 * unsafe.Sizeof(int(0))

    i := (*int)(unsafe.Pointer(ptr))
    fmt.Println(*i) // 4
}

程序显示转换可能发生在重新分片之后。原始的支持数组仍然有六个元素,所以人们可能会想为什么(*[6]int)(b)cap(b) == 6时会出现运行时崩溃。
这一点已经被提出来了。值得记住的是,与切片不同,数组具有 * 固定大小 *,因此它不需要容量的概念,只需要长度:

a := [4]int{1,2,3,4}
fmt.Println(len(a) == cap(a)) // true
bfnvny8b

bfnvny8b7#

不用。切片本身就足够了。go lang中的数组应该被视为切片的底层结构。在任何情况下,只使用切片。你不必自己使用数组。你只需要按照切片语法来做所有的事情。数组只适用于计算机。在大多数情况下,切片更好,代码更清晰。即使在其他情况下,切片也足以反映你的想法。

相关问题