如何在Go中获取切片的基础数组?

Jim*_*ong 15 arrays go slice

假设我有以下长度为3的整数:

nums := [3]int{1,2,3}

然后我抓住前两个项目的切片

numSlice := nums[:2]

cap在两种情况下调用numSlice和nums都会产生3,并len分别产生2和3.

如果我然后附加到slice(numSlice = append(numSlice, 10)),nums则现在是底层数组()[1 2 10].cap两者都保持为3,因为切片的底层数组是相同的,而切片的len现在是3.

但是,如果我再次附加到该切片(numSlice = append(numSlice, 20)),则切片的基础数组必须更改 - 我们看到cap现在已经为numSlice加倍并且len现在为4.

对于过于简单的解释感到抱歉,只是自己走过去,但有人可以向我解释底层阵列发生了什么,以及如何获得对新阵列的引用?

Sim*_*mon 20

首先,如果你还没有,你应该阅读这篇关于切片内部的官方博客文章.这应该清理一切.

我们访问底层的数组,你可以使用的组合reflectunsafe.特别是,reflect.SliceHeader 包含一个Data字段,该字段包含指向切片的基础数组的指针.

根据unsafe包装文件改编的示例:

s := []int{1, 2, 3, 4}
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&s))
data := *(*[4]int)(unsafe.Pointer(hdr.Data))
Run Code Online (Sandbox Code Playgroud)

  • 值得注意的是`Data`可能不指向后备数组的开始:只是切片的第0个元素.因此,在`numSlice:= nums [1:]`的情况下,它实际上最终将指向数组的第二个元素.[实施例](http://play.golang.org/p/8fCCz9d7rG) (3认同)
  • @Highstead 根本不是一个愚蠢的问题!当我们使用反射时,性能通常会受到影响,但在这种情况下,它应该相当不错。如果我正确理解 https://go.godbolt.org/z/h6v67McWr,编译器实际上从这里的反射/不安全代码生成*更少的*代码,而不是从 1.17 以来可能更短的代码,因为反射/不安全代码不没有错误检查。 (2认同)

Hig*_*ker 5

作为一个提示,回答你的第二个问题。从Go 1.17开始,你可以这样做

(*[2]int)(numSlice)
Run Code Online (Sandbox Code Playgroud)

操场

package main

import (
    "fmt"
)

func main() {
    nums := [3]int{1, 2, 3}
    numSlice := nums[:2]
    underArr1 := (*[2]int)(numSlice)
    fmt.Println(&underArr1[0]) //0xc000016018

    numSlice = append(numSlice, 10)
    underArr2 := (*[3]int)(numSlice)
    fmt.Println(&underArr2[0]) //0xc000016018 - same
    fmt.Println(nums) // [1 2 10]

    numSlice = append(numSlice, 20)
    underArr3 := (*[3]int)(numSlice)
    fmt.Println(&underArr3[0]) //0xc000078030 - different
    fmt.Println(cap(numSlice)) // 6
}
Run Code Online (Sandbox Code Playgroud)

老实说,您不必转换为数组指针来查看地址,我这样做只是为了回答您的第二个问题。

该行为确实如您所描述的那样。当您追加 时10,底层数组中仍然留有一个字段(因为它的长度为 3,但您的 numSlice 为 2),即使它当前被 a 占用3,它仍然可以使用,并被3覆盖10

当您附加 a 时20,没有剩余字段,因此它会创建一个新的基础数组(很可能有 6 个字段长,两倍大),并从原始数组复制所有数据,并将指针移动到该数组。