在Golang中,如何将切片转换为数组

时间:2013-09-29 02:22:13

标签: go

我是Go的新手并尝试编写一个读取RPM文件的应用程序。每个块的开头都有一个[4]byte的Magic char。

这是我的结构

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

7 个答案:

答案 0 :(得分:32)

内置方法 copy 只会将切片复制到切片而不是切片到数组。

你必须欺骗复制以认为数组是一个切片

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

或使用for循环进行复制:

for index, b := range someSlice {

    varLead.Magic[index] = b

}

或者像zupa一样使用文字。我已经添加了他们的工作示例。

Go Playground

答案 1 :(得分:11)

您已在该结构中分配了四个字节,并希望为该四字节部分分配值。没有复制就没有概念性的方法。

查看copy内置的内容,了解如何执行此操作。

答案 2 :(得分:7)

试试这个:

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

答案 3 :(得分:2)

无需复制,您可以在下一个 Go 1.17(2021 年第 3 季度)中将切片转换为数组指针

这称为“取消切片”,再次返回指向 underlying array of a slice 的指针,无需任何复制/分配:

https://blog.golang.org/slices-intro/slice-1.png

参见 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)      // &amp;s2[0] == &amp;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];
}

允许编译器只检查所有边界一次,并给出有关超出范围索引的编译时错误。

Also

<块引用>

一个很好用的例子是使树节点或链表节点的类尽可能小,这样你就可以将尽可能多的类塞进 L1 缓存行中。
这是通过每个节点都有一个指向左子节点的指针来完成的,并且右子节点被指向左子节点的指针 + 1 访问。
这为右节点指针节省了 8 个字节。

为此,您必须预先分配向量或数组中的所有节点,以便它们按顺序排列在内存中,但在您需要提高性能时值得这样做。
(这也有额外的好处,即预取器能够在性能方面提供帮助 - 至少在链表的情况下)

你可以几乎在 Go 中做到这一点:

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

除了无法从底层切片中获取 *[2]node

答案 4 :(得分:0)

您可以通过一次阅读完成整个过程,而不是单独阅读每个字段。如果字段是固定长度的,那么您可以这样做:

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)

答案 5 :(得分:0)

<块引用>

如果需要,我更好奇如何从 []byte 类型变为 [4]byte 类型?

从 Go 1.17 开始,您可以directly convert 切片到数组指针。使用 Go 的类型转换语法 T(x),您可以这样做:

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

注意数组的长度不能大于切片的长度,否则转换会panic。

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

这种转换的优点是不进行任何复制,因为它只是产生一个指向底层数组的指针。

当然可以解引用数组指针来获取非指针数组变量,所以下面的方法也行:

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

然而,取消引用和赋值会巧妙地复制,因为 arr 变量现在是 initialized 到转换表达式产生的值。要清楚(为简单起见使用整数):

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) where cap(b) == 6 会发生运行时恐慌。

这实际上是brought up。值得记住的是,与切片不同,数组具有固定大小,因此它不需要容量概念,只需要长度:

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

答案 6 :(得分:-4)

唐&#39;吨。切片本身就足够了。 go lang中的数组应该被视为切片的底层结构。在每种情况下,只使用切片。你不必自己排队。您只需按切片语法执行所有操作。数组仅适用于计算机。在大多数情况下,切片更好,代码清晰。即使在其他情况下,切片仍然足以反映您的想法。