Go 切片中指针的行为

Go 切片中指针的行为,go,slice,Go,Slice,从切片创建切片的行为是什么?当您有如下定义的切片时: s := []int{2, 3, 5, 7, 11, 13} s = s[:3] // s = [2 3 5] s = s[:cap(s)] // s = [2 3 5 7 11 13] 您希望修改切片,如下所示: s := []int{2, 3, 5, 7, 11, 13} s = s[:3] // s = [2 3 5] s = s[:cap(s)] // s = [2 3 5 7 11 13] 它实际上可以“向右扩展”您的切片。

从切片创建切片的行为是什么?当您有如下定义的切片时:

s := []int{2, 3, 5, 7, 11, 13}
s = s[:3]
// s = [2 3 5]
s = s[:cap(s)]
// s = [2 3 5 7 11 13]
您希望修改切片,如下所示:

s := []int{2, 3, 5, 7, 11, 13}
s = s[:3]
// s = [2 3 5]
s = s[:cap(s)]
// s = [2 3 5 7 11 13]
它实际上可以“向右扩展”您的切片。不起作用的是:

s = s[2:]
// s = [5 7 11 13]
s = [:cap(s)]
// s = [5 7 11 13]

因此,在本例中,当您创建新切片时,您不能“保留”前两个元素。即使基础数组没有更改,也不能更改指向该数组开头的指针,对吗?这是为什么?

正如@JimB在评论中指出的,这是由于切片在Go中的作用

从根本上说,切片头是一个包含3个元素的结构:指向第一个元素的指针、当前数据的长度以及从“第一个元素”指针测量的基础数组的总容量(即使它只是实际分配的基础数组的一部分)。它没有关于基础数组的其他信息

当您通过
s[:x]
从末尾切断数据时,您正在创建一个新的切片标头,该标头具有不同的长度字段,但容量和第一个元素指针相同。因此,您可以再次将该片扩展到该最大容量,因为运行时知道该内存已分配且可安全访问

当您使用
s[x://code>时,您正在创建一个新的片头,除了长度减少之外,还使用指向新的第一个元素的不同初始指针和减少的容量。您不能“撤消”它以指向原始的第一个元素,因为slice结构除了指针和容量字段之外,没有关于原始切片或底层数组的信息

例如,如果使切片
s:=[]int{2,3,5,7,11,13}
,则切片头可能包含:

ptr: 0x00000000
len: 6
cap: 6
如果然后调用
s=s[:3]
,则切片头将包含:

ptr: 0x00000000
len: 3
cap: 6
请注意,只有len发生了更改。如果随后调用
s=s[:cap(s)]
,则运行时会发现容量可以支持该切片操作,这意味着基础阵列至少分配了那么多“插槽”,并可以毫无问题地扩展切片。由于原始数据仍在这些“插槽”中,因此可以从中提取与原始阵列功能等效的数据:

ptr: 0x00000000
len: 6
cap: 6
但是,如果调用
s=s[2://code>,则会得到以下结果:

ptr: 0x00000010
len: 4
cap: 4
请注意,除了长度更改之外,指针还增加了16个字节(64位系统上的大小为两个
int
s),并且容量字段也减少了,因为底层数组仅分配了相对于该指针的4个“插槽”

现在,运行时没有关于底层指针的附加信息,除了该头!从该标头,运行时无法知道原始基础数组的大小,或者它的原始起点在哪里,或者即使当前切片不是从该原始起点开始。因此,尝试将片重置回原始起点是不合法的,因为运行时无法验证内存是否已分配且安全。这是一个有意的设计决策,因为GO是专门设计的,不允许在C++程序中常见的不明智和高度错误的任意指针运算类型。 此外,您对
s=s[:cap(s)]
的调用正在使用存储在切片标头中的新(减少的)容量。虽然您的第一次这种性质的调用相当于
s=s[:6]
,因为这就是原始片的容量,但现在的调用相当于
s=s[:4]
,因为移动片的指针也会降低备份数组的容量(因为该容量是从该指针指向的元素测量的,而不是从实际备份数组的第一个元素测量的)


如果Go运行时将切片作为指针(指向备份数组中的绝对第一个元素)、长度、容量和偏移量进行跟踪,那么您可能会想要什么。但是,它不会这样做。部分原因是这将表示切片头的大小增加了33%(专门设计为尽可能轻)部分原因是,正如@JimB所指出的,该语言的开发人员认为没有必要增加复杂性,因为如果您认为有必要,您可以自己轻松地保留原始切片头上的句柄。

因为它们就是这样指定的?我不确定真正的问题是什么,但这可能会有帮助吗?这不是问题king sense对我来说,你可以“扩展”你的切片到右边,但不能到左边。你做对了。切片扩展到“右边”(更高的索引)。这就是切片的工作方式。请参阅Jim链接的帖子,和以获得进一步的解释。你不能“扩展”它,容量总是存在。至于为什么片不记录原始数组分配和当前分配的开始,并提供一个符号或函数来回收该空间,这是一个被认为不必要的复杂问题。如果需要保留对起始元素的引用,请保留原始片的副本。@JimB我想留下你的评论作为回答:D,这个问题似乎完全正确,没有必要投反对票。