如果返回作为函数或方法的局部变量的数组的切片会怎样?Go是否将数组数据复制到使用创建的切片中make()?容量将匹配切片大小还是阵列大小?
make()
func foo() []uint64 { var tmp [100]uint64 end := 0 ... for ... { ... tmp[end] = uint64(...) end++ ... } ... return tmp[:end] }
这在Spec:Slice expressions中有详细说明。
不会复制该数组,但slice表达式的结果将是引用该数组的slice。在Go中,从函数或方法返回局部变量或它们的地址是绝对安全的,Go编译器执行逸出分析以确定值是否可以逸出该函数,以及是否可以逸出(或是否不能证明某个值)可能无法转义),它会在堆上分配它,因此函数返回后将可用。
切片表达式:tmp[:end]意思是tmp[0:end](因为缺少的low索引默认为零)。既然你没有指定容量,则默认为len(tmp) - 0这是len(tmp)这是100。
tmp[:end]
tmp[0:end]
low
len(tmp) - 0
len(tmp)
100
您还可以使用 完整切片表达式 来控制结果切片的容量,该 表达式 的形式为:
a[low : high : max]
将结果切片的容量设置为max - low。
max - low
更多示例来阐明所得切片的长度和容量:
var a [100]int s := a[:] fmt.Println(len(s), cap(s)) // 100 100 s = a[:50] fmt.Println(len(s), cap(s)) // 50 100 s = a[10:50] fmt.Println(len(s), cap(s)) // 40 90 s = a[10:] fmt.Println(len(s), cap(s)) // 90 90 s = a[0:50:70] fmt.Println(len(s), cap(s)) // 50 70 s = a[10:50:70] fmt.Println(len(s), cap(s)) // 40 60 s = a[:50:70] fmt.Println(len(s), cap(s)) // 50 70
在Go Playground上尝试一下。
如果要在堆栈上分配它,则不能返回任何指向它(或其一部分)的值。如果将其分配在堆栈上,将无法保证返回后仍保持可用状态。
一种可能的解决方案是将指向数组的指针作为函数的参数传递(并且您可以返回一个切片,指定该函数填充的 有用 部分),例如:
func foo(tmp *[100]uint64) []uint64 { // ... return tmp[:end] }
如果调用者函数在堆栈上创建了数组,则不会导致“重新分配”或“移动”到堆:
func main() { var tmp [100]uint64 foo(&tmp) }
运行go run -gcflags '-m -l' play.go,结果是:
go run -gcflags '-m -l' play.go
./play.go:8: leaking param: tmp to result ~r1 level=0 ./play.go:5: main &tmp does not escape
该变量tmp不会移到堆。
tmp
请注意,这[100]uint64被认为是要在堆栈上分配的小数组。
[100]uint64