在 Go 语言中,当传递 []Item 类型的参数给函数时,会拷贝 slice 的头部信息,但不会拷贝底层数组的数据。
具体来说:
Slice (切片) 的头部信息: Go 的
slice类型实际上是一个结构体,包含三个字段:pointer: 指向底层数组的指针。length: 切片的长度。capacity: 切片的容量。
传递过程: 当将
[]Item类型的变量传递给函数时,会拷贝这个包含pointer,length, 和capacity的结构体。 也就是说,函数接收到的是一个新的slice结构体,但这个新的slice结构体中的pointer仍然指向同一个底层数组。
这意味着:
函数内部对
slice的修改可能影响外部:修改
slice中的元素: 由于slice指向的是同一个底层数组,如果函数修改了slice中的Item元素,那么原始slice也会受到影响,因为它们共享底层数组。修改
slice的长度: 如果函数通过append操作改变了slice的长度,可能会导致函数内部的slice使用一个新的底层数组。在这种情况下,原始slice不会受到影响。 只有当append操作在函数内部没有超出原始slice的capacity时,对长度的修改才会影响到原始slice。
函数内部对
slice头部信息的修改不影响外部:- 修改
slice的长度或容量(但未超出 capacity): 如果函数修改了传入slice的长度,但未超出原始slice的capacity,则原始slice会受到长度变化的影响。 - 修改
slice的长度或容量 (超出 capacity): 如果函数修改了传入slice的长度,且超过了原始slice的capacity,Go 会分配一个新的底层数组,并且将数据复制到新的数组中。 在这种情况下,函数内部的slice就不再和原始slice共享同一个底层数组,因此原始slice不会受到影响。
- 修改
总结:
当传递 []Item 时,传递的是 slice 结构体的副本,指向同一个底层数组。 因此:
- 修改
slice元素的会影响原始slice。 - 如果
append操作没有超出原始slice的capacity,修改slice的长度会影响原始slice。 - 如果
append操作超出了原始slice的capacity,修改slice的长度不会影响原始slice。
示例代码:
1 | package main |
输出结果 (可能会因为 Go 版本而略有不同,但核心概念一致):
1 | Original slice (before): [{Item 1 5} {Item 2 10}] len: 2 cap: 2 |
解释:
items[0].Name = "Updated Item"修改了原始slice的第一个元素。- 第一次
append因为没有超过原始 capacity(2), 这时候,长度改变会影响到原始 slice. - 第二次
append因为超过了原始 capacity(2),会分配一个新的底层数组,所以原始slice最终没有受到第二次append带来的影响
因此,在 Go 中传递 slice 时要特别注意,理解它是按值传递(但底层数组是指针)的性质,避免产生意料之外的副作用。 如果你希望完全避免修改原始 slice,可以在函数内部创建一个 slice 的副本,然后操作副本。 例如: copiedSlice := make([]Item, len(items)); copy(copiedSlice, items)。 然后函数操作copiedSlice。