英文:
How do you convert a slice into an array?
问题
我正在尝试编写一个读取RPM文件的应用程序。每个块的开头都有一个Magic字符[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]
我在网上搜索,不确定如何从切片转换为数组(而不进行复制)。我可以将Magic定义为[]byte
(甚至是uint64
),但我更好奇的是,如果需要的话,如何从[]byte
类型转换为[4]byte
类型?
英文:
I am trying to write an application that reads RPM files. The start of each block has a Magic char of [4]byte
.
Here is my struct
type Lead struct {
Magic [4]byte
Major, Minor byte
Type uint16
Arch uint16
Name string
OS uint16
SigType uint16
}
I am trying to do the following:
lead := Lead{}
lead.Magic = buffer[0:4]
I am searching online and not sure how to go from a slice to an array (without copying). I can always make the Magic []byte
(or even uint64
), but I was more curious on how would I go from type []byte
to [4]byte
if needed to?
答案1
得分: 50
内置方法copy只能将一个切片复制到另一个切片,而不能将切片复制到数组。
你需要欺骗copy,让它认为数组是一个切片:
copy(varLead.Magic[:], someSlice[0:4])
或者使用for循环进行复制:
for index, b := range someSlice {
varLead.Magic[index] = b
}
或者像zupa一样使用字面量。我在他们的示例上进行了扩展。
英文:
The built in method copy will only copy a slice to a slice NOT a slice to an array.
You must trick copy into thinking the array is a slice
copy(varLead.Magic[:], someSlice[0:4])
Or use a for loop to do the copy:
for index, b := range someSlice {
varLead.Magic[index] = b
}
Or do as zupa has done using literals. I have added onto their working example.
答案2
得分: 12
请尝试这样做:
将 buf[0:4] 的内容复制到 lead.Magic 的前四个元素中。
英文:
Try this:
copy(lead.Magic[:], buf[0:4])
答案3
得分: 11
你在结构体内分配了四个字节,并想要给这四个字节的部分赋值。没有概念上的方法可以在不复制的情况下实现这一点。
可以查看copy
内置函数来了解如何实现。
英文:
You have allocated four bytes inside that struct and want to assign a value to that four byte section. There is no conceptual way to do that without copying.
Look at the copy
built-in for how to do that.
答案4
得分: 6
> Go <del>1.18</del> <del>1.19</del> 1.20将支持从切片转换为数组:golang/go
问题46505。
因此,自Go 1.18以来,切片复制2的实现可以写成:
*(*[N]T)(d) = [N]T(s)
或者,如果允许将转换表示为L值,甚至更简单:
[N]T(d) = [N]T(s)
在没有复制的情况下,您可以在下一个Go 1.17(2021年第三季度)中将切片转换为数组指针。
这被称为“取消切片”,可以将切片的底层数组的指针返回,同样不需要任何复制/分配:
请参阅golang/go
问题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) // 恐慌:len([4]byte) > len(s)
var t []string
t0 := (*[0]string)(t) // t0 == nil
t1 := (*[1]string)(t) // 恐慌: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中所解释的:
> 这样做的一个动机是,使用数组指针允许编译器在编译时对常量索引进行范围检查。
>
> 像这样的函数:
>
>go >func foo(a []int) int >{ > return a[0] + a[1] + a[2] + a[3]; >} >
>
> 可以转换为:
>
>go >func foo(a []int) int >{ > b := (*[4]int)(a) > return b[0] + b[1] + b[2] + b[3]; >} >
> 允许编译器仅检查一次所有边界,并在索引超出范围时给出编译时错误。
还有:
> 一个常用的例子是使类尽可能小,以用于树节点或链表节点,以便将尽可能多的节点塞入L1缓存行中。
> 这是通过每个节点只有一个指向左子节点的指针来实现的,而通过左子节点的指针+1来访问右子节点。
> 这样可以节省右节点指针的8个字节。
>
> 要做到这一点,您必须预先分配一个向量或数组中的所有节点,以便它们在内存中按顺序布局,但在需要性能时这是值得的。
> (这还有一个额外的好处,即预取器能够在性能方面提供帮助-至少在链表的情况下)
>
> 在Go中,您几乎可以这样做:
>go > type node struct { > value int > children *[2]node > } >
> 除了没有办法从底层切片获取*[2]node
。
Go 1.20(2023年第一季度):这个问题已经在CL 430415、428938(类型)、430475(反射)和429315(规范)中得到解决。
英文:
Tapir Liui (auteur de Go101) twitte:
> Go <del>1.18</del> <del>1.19</del> 1.20 will support conversions from slice to array: golang/go
issues 46505.
So, since Go 1.18,the slice copy2 implementation could be written as:
*(*[N]T)(d) = [N]T(s)
or, even simpler if the conversion is allowed to present as L-values:
[N]T(d) = [N]T(s)
Without copy, you can convert, with the next Go 1.17 (Q3 2021) a slice to an array pointer.
This is called "un-slicing", giving you back a pointer to the underlying array of a slice, again, without any copy/allocation needed:
See golang/go
issue 395: spec: convert slice x into array pointer
, now implemented with CL 216424/, and commit 1c26843
> Converting a slice to an array pointer yields a pointer to the underlying array of the slice.
If the length of the slice is less than the length of the array,
a run-time panic occurs.
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)
So in your case, provided Magic
type is *[4]byte
:
lead.Magic = (*[4]byte)(buffer)
Note: type aliasing will work too:
type A [4]int
var s = (*A)([]int{1, 2, 3, 4})
Why convert to an array pointer? As explained in issue 395:
> One motivation for doing this is that using an array pointer allows the compiler to range check constant indices at compile time.
>
> A function like this:
>
>go
>func foo(a []int) int
>{
> return a[0] + a[1] + a[2] + a[3];
>}
>
>
> could be turned into:
>
>go
>func foo(a []int) int
>{
> b := (*[4]int)(a)
> return b[0] + b[1] + b[2] + b[3];
>}
>
> allowing the compiler to check all the bounds once only and give compile-time errors about out of range indices.
Also:
> One well-used example is making classes as small as possible for tree nodes or linked list nodes so you can cram as many of them into L1 cache lines as possible.
This is done by each node having a single pointer to a left sub-node, and the right sub-node being accessed by the pointer to the left sub-node + 1.
This saves the 8-bytes for the right-node pointer.
>
> To do this you have to pre-allocate all the nodes in a vector or array so they're laid out in memory sequentially, but it's worth it when you need it for performance.
(This also has the added benefit of the prefetchers being able to help things along performance-wise - at least in the linked list case)
>
> You can almost do this in Go with:
>go
> type node struct {
> value int
> children *[2]node
> }
>
> except that there's no way of getting a *[2]node
from the underlying slice.
Go 1.20 (Q1 2023): this is addressed with CL 430415, 428938 (type), 430475 (reflect) and 429315 (spec).
答案5
得分: 4
Go 1.20
你可以使用常规的转换语法 T(x)
将切片转换为数组。数组的长度不能大于切片的长度:
func main() {
slice := []int64{10, 20, 30, 40}
array := [4]int64(slice)
fmt.Printf("%T\n", array) // [4]int64
}
Go 1.17
从 Go 1.17 开始,你可以直接将切片转换为数组指针。使用 Go 的类型转换语法 T(x)
可以实现这一点:
slice := make([]byte, 4)
arrptr := (*[4]byte)(slice)
请注意,数组的长度不能大于切片的长度,否则转换将会引发 panic。
bad := (*[5]byte)(slice) // 引发 panic:切片长度 < 数组长度
这种转换的优点是不会进行任何复制,因为它只是返回底层数组的指针。
当然,你可以解引用数组指针以获得非指针数组变量,所以下面的代码也是有效的:
slice := make([]byte, 4)
var arr [4]byte = *(*[4]byte)(slice)
然而,解引用和赋值会进行隐式的复制,因为 arr
变量现在被初始化为转换表达式的结果值。为了清楚起见(使用 int 作为示例):
v := []int{10,20}
a := (*[2]int)(v)
a[0] = 500
fmt.Println(v) // [500 20](改变了,两者都指向同一个底层数组)
w := []int{10,20}
b := *(*[2]int)(w)
b[0] = 500
fmt.Println(w) // [10 20](未改变,b 持有一个副本)
有人可能会想为什么转换检查切片的长度而不是容量(我也曾经这样想过)。考虑下面的程序:
func main() {
a := []int{1,2,3,4,5,6}
fmt.Println(cap(a)) // 6
b := a[:3]
fmt.Println(cap(a)) // 仍然是 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)
中会发生运行时 panic,而 cap(b) == 6
。
这实际上已经被提出过。值得记住的是,与切片不同,数组具有固定大小,因此它不需要容量的概念,只有长度:
a := [4]int{1,2,3,4}
fmt.Println(len(a) == cap(a)) // true
英文:
Go 1.20
You can convert from a slice to an array directly with the usual conversion syntax T(x)
. The array's length can't be greater than the slice's length:
func main() {
slice := []int64{10, 20, 30, 40}
array := [4]int64(slice)
fmt.Printf("%T\n", array) // [4]int64
}
Go 1.17
Starting from Go 1.17 you can directly convert a slice to an array pointer. With Go's type conversion syntax T(x)
you can do this:
slice := make([]byte, 4)
arrptr := (*[4]byte)(slice)
Keep in mind that the length of the array must not be greater than the length of the slice, otherwise the conversion will panic.
bad := (*[5]byte)(slice) // panics: slice len < array len
This conversion has the advantage of not making any copy, because it simply yields a pointer to the underlying array.
Of course you can dereference the array pointer to obtain a non-pointer array variable, so the following also works:
slice := make([]byte, 4)
var arr [4]byte = *(*[4]byte)(slice)
However dereferencing and assigning will subtly make a copy, since the arr
variable is now initialized to the value that results from the conversion expression. To be clear (using ints for simplicity):
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)
<hr>
One might wonder why the conversion checks the slice length and not the capacity (I did). Consider the following program:
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
}
The program shows that the conversion might happen after reslicing. The original backing array with six elements is still there, so one might wonder why a runtime panic occurs with (*[6]int)(b)
where cap(b) == 6
.
This has actually been brought up. It's worth to remember that, unlike slices, an array has fixed size, therefore it needs no notion of capacity, only length:
a := [4]int{1,2,3,4}
fmt.Println(len(a) == cap(a)) // true
答案6
得分: 1
你可以尝试使用一次读取的方式完成整个操作,而不是逐个字段进行读取。如果字段是固定长度的,你可以这样做:
lead := Lead{}
// 创建一个读取器,以便提供字节,这样你就不必跟踪缓冲区中的位置
reader := bytes.NewReader(buffer)
// 将每个字段读入到Lead结构体中,例如Magic读取buffer[0:4],
// Major读取buffer[5],Minor读取buffer[6],以此类推...
binary.Read(reader, binary.LittleEndian, &lead)
英文:
You might be able to do the whole thing with one read, instead of reading individually into each field. If the fields are fixed-length, then you can do:
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)
答案7
得分: -3
不需要。Slice本身对于所有目的来说已经足够了。在Go语言中,数组应该被视为切片的底层结构。在每种情况下,只使用切片即可。你不需要自己创建数组。你只需要使用切片语法来完成所有操作。数组只是为了计算机而存在的。在大多数情况下,切片在代码中更好、更清晰。即使在其他情况下,切片仍然足以表达你的想法。
英文:
Don't. Slice itself is suffice for all purpose. Array in go lang should be regarded as the underlying structure of slice. In every single case, use only slice. You don't have to array yourself. You just do everything by slice syntax. Array is only for computer. In most cases, slice is better, clear in code. Even in other cases, slice still is sufficient to reflex your idea.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论