切片中指针的行为

时间:2017-12-14 16:17:55

标签: go slice

从切片创建切片的行为是什么?如果像这样定义切片:

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]

它实际上适用于"向右扩展"你的切片。哪个不起作用:

inputRef={el => { 
  if (el && el.id == this.state.selectedBuildingRef) {
    this.myelement.scrollIntoView();
    window.scrollBy(0, -65);
  } 
}}

所以你不能"保留"在这种情况下,当您创建新切片时,前两个元素。即使底层数组没有改变,你也无法将指针改为该数组的开头,对吗?那是为什么?

1 个答案:

答案 0 :(得分:2)

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

基本上,切片头是一个包含3个元素的结构:指向第一个元素的指针,当前数据的长度,以及从&#34开始测量的基础数组的总容量。元素"指针(即使它只是实际分配的底层数组的一部分)。它没有关于底层阵列的其他信息。

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

当您使用s[x:]时,除了缩短的长度之外,您还要创建一个新的切片标头,其中包含指向新的第一个元素的不同初始指针和减少的容量。你不能撤消"指向原始的第一个元素,因为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)],运行时会看到容量可以支持切片操作,这意味着底层数组至少有那么多" slot"分配,并扩展切片没有问题。由于原始数据仍在那些" slot"中,因此您将退回功能上与原始数组相同的数据:

ptr: 0x00000000
len: 6
cap: 6

但是,如果您致电s = s[2:],则会收到以下信息:

ptr: 0x00000010
len: 4
cap: 4

请注意,除了长度更改之外,指针还增加了16个字节(64位系统上的两个int的大小),并且容量字段也已减少,因为底层数组只有4"插槽"相对于该指针分配。

现在,运行时没有关于底层指针的其他信息,除了那个头!从那个头开始,运行时无法知道原始底层数组的大小是或者它的原始起始点是,或者即使当前切片没有从该原始起始点开始。因此,尝试将切片重置回原始起点是不合法的,因为运行时无法验证该内存是否已分配且安全。这是一个有意的设计决策,因为Go专门设计为不允许在C ++程序中常见的不明智和高错误的任意指针算法类型。

此外,您对s = s[:cap(s)]的呼叫正在使用切片标头中存储的(减少的)容量。虽然您第一次使用这种性质的电话相当于s = s[:6],但由于这是原始片段的容量,因此调用现在等同于s = s[:4],因为移动片段&# 39; s指针也减少了后备阵列的容量(因为该容量是从该指针指向的元素测量的,不是实际后备阵列的第一个元素)。

如果Go运行时改为将切片跟踪为指针(指向支持数组中的绝对第一个元素),长度,容量和偏移量,那么你想要的是可能。但是,它不会这样做。部分原因是因为这将代表切片头大小增加33%(特别是尽可能轻量级),部分原因是,正如@JimB指出的那样,该语言的开发人员决定这种额外的复杂性是不必要的,因为如果您认为有必要,您可以轻松地自己处理原始切片标题。