当前位置: 首页 > 面试题库 >

切片中指针的行为

屈健柏
2023-03-14
问题内容

从切片创建切片的行为是什么?当您像这样定义切片时:

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在注释中指出的,这是由于slice在Go中的功能。

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

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

当使用时s[x:],您将创建一个新的slice头,它具有指向新的第一个元素的不同初始指针,并具有减小的容量以及减小的长度。您不能“撤消”该操作以使其指向原始的第一个元素,因为切片结构除了指针和容量字段外,没有有关原始切片或基础数组的信息。

例如,如果您制作slice s := []int{2, 3, 5, 7, 11, 13},则slice头可能包含:

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:],则会得到以下信息:

ptr: 0x00000010
len: 4
cap: 4

请注意,除了更改长度之外,指针还增加了16个字节(在int64位系统上为2 s
的大小),并且由于下面的数组仅分配了4个“插槽”,所以容量字段也已减小。相对于该指针。

现在,运行时 除了该标头之外没有关于基础指针的其他信息! 从该标头中,运行时 无法
知道原始基础数组的大小或原始起点的位置,甚至无法知道当前切片不是从该原始起点开始的。结果,尝试将片重置回原始起点是不合法的,因为运行时无法验证内存是否已分配且安全。这是一个有意的设计决策,因为Go专门设计为不允许使用C
++程序中常见的不良建议和高度笨拙的任意指针算法。

此外,您的呼叫s = s[:cap(s)]使用的是切片头中存储的 (减少的)容量。虽然您第一次进行此调用等效于s = s[:6],因为这是原始切片的容量,但现在调用等效于s = s[:4],因为移动切片的指针还减少了后备数组的容量(因为该容量是从所指向的元素测量的)指向该指针, 而不是 实际后备数组的第一个元素)。

如果Go运行时改为将切片作为指针(指向后备数组中的绝对第一个元素),长度,容量和 offset进行
跟踪,则可以实现所需的操作。但是,它不会这样做。部分原因是因为这将导致切片标头的大小增加33%(专门用于尽可能轻量级),部分原因是正如@JimB所指出的那样,该语言的开发人员决定增加额外的复杂性不必要,因为如果您认为有必要,可以轻松地自己处理原始切片标头。



 类似资料:
  • 值得注意点的是切片的本质就是一个指针指向数组,所以指向切片的指针是一个二级指针 package main import "fmt" func main() { // 1.定义一个切片 var sce[]int = []int{1, 3, 5} // 2.打印切片的地址 // 切片变量中保存的地址, 也就是指向的那个数组的地址 sce = 0xc0420620a0 f

  • 问题内容: 我有以下代码: 但是我想将一个指向字节切片的指针传递给另一个函数,并在那里切片,所以类似: 它给我一个错误,我不能在函数参数中使用type 作为类型,并且不能对type进行切片。怎么了?默认情况下切片不是通过引用传递的吗?我试图在没有指针的情况下执行此操作,但是它不起作用- 正在复制数组。我怎样才能做到这一点? 问题答案: 该错误来自您尚未发布的拼写错误(您尝试传递一个切片而不是指向的

  • 问题内容: 我想将字符串切片转换为指向字符串的指针切片 %!p(string = a)=>字符串 %!p(string = b)=>字符串 %!p(string = c)=>字符串 [0xc42000e1d0 0xc42000e1d0 0xc42000e1d0] 据我了解, 我的变量似乎是一个字符串,而不是指向字符串的指针。 因此应从迭代时复制。 显然我不正确,因为地址仍然相同。如果值不是指针,该

  • 问题内容: 当尝试在切片指针上移动时,我一直收到此错误。 我究竟做错了什么? 这是结构: 这是Class结构: 问题答案: 您假设指向切片的指针将在迭代时自动取消引用。 事实并非如此,没有理由,因为切片已经是一种指针,从而使指向切片的指针完全无用。 从有效出发: 如果函数采用slice参数,则对slice元素所做的更改将对调用者可见,这类似于将指针传递给基础数组。 在内部,切片是由 指向基础数组中

  • 问题内容: 我不理解以下代码的行为。在创建作为结构指针切片的匹配结构列表时,代码始终会打印原始数组的最后一个元素(实际上不是匹配项),它会打印12和12。但是,如果将匹配项更改为[]窗口小部件代替[] * Widget,然后将输出10和11。 为什么是这样? 问题答案: 那是因为当您使用指针时,您将添加到数组。 请注意,实际上这是循环中使用的局部变量,因此,这不是您要添加到数组中的地址。 (即使变

  • 问题内容: 我想有一个通用的方法,无论它是作为指针,切片还是数组提供的,都将始终返回结构值。 我对此的处理方式如下: 去游乐场 如您所见,问题出在从a 或。中获取结构。 我如何扩展上面的函数以从数组或切片中获取struct的值? 更新:我要做的就是将变成。 问题答案: 如果你只是想要的类型,即使片是零,你可以使用像这样: 关于,来自http://golang.org/pkg/reflect/#Ty