34

我正在尝试编写一个读取 RPM 文件的应用程序。每个块的开头都有一个 Magic char [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

4

7 回答 7

45

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

你必须欺骗copy认为数组是一个切片

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

或使用 for 循环进行复制:

for index, b := range someSlice {

    varLead.Magic[index] = b

}

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

去游乐场

于 2014-01-28T07:45:25.703 回答
10

您已在该结构内分配了四个字节,并希望为该四个字节部分分配一个值。没有复制就没有概念上的方法可以做到这一点。

查看copy内置的如何做到这一点。

于 2013-09-29T02:57:10.867 回答
10

尝试这个:

copy(lead.Magic[:], buf[0:4])
于 2013-09-30T11:23:10.350 回答
4

Tapir LiuiGo101 导演推特

Go 1.18 将支持从切片到数组的转换:golang/go问题 46505

所以,从 Go 1.18 开始,切片 copy2 的实现可以写成:

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

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

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

如果没有复制,您可以使用下一个 Go 1.17(2021 年第三季度)将切片转换为数组指针

这称为“un-slicing”,再次为您返回一个指向slice 底层数组的指针,而无需任何复制/分配:

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

请参阅golang/go问题 395:spec: convert slice x into array pointer,现在使用CL 216424/实现,并提交 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})

为什么要转换为数组指针?如问题 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 个字节。

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

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

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

除了没有办法*[2]node从底层切片中获取 a 。

于 2021-04-21T16:13:17.350 回答
1

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

从 Go 1.17 开始,您可以直接将切片转换为数组指针。使用 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变量现在已初始化为转换表达式产生的值。要清楚(为简单起见使用整数):

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

这其实已经提出来了。值得记住的是,与切片不同,数组具有固定大小,因此它不需要容量的概念,只需要长度:

a := [4]int{1,2,3,4}
fmt.Println(len(a) == cap(a)) // true
于 2021-07-11T13:18:36.413 回答
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)
于 2017-07-03T15:05:32.950 回答
-4

不。Slice 本身就足以满足所有目的。go lang 中的数组应该算是 slice 的底层结构。在每种情况下,只使用切片。您不必自己排列。您只需通过切片语法完成所有操作。阵列仅适用于计算机。在大多数情况下,切片更好,代码清晰。即使在其他情况下, slice 仍然足以反映您的想法。

于 2015-05-27T15:08:13.847 回答